Skip to content

seq -w: positive exponents in scientific-notation operands produce wrong padding width (leading zero not cancelled) #14153

Description

@Poojax21

Summary

compute_num_digits in src/uu/seq/src/numberparse.rs counts integral digits of an operand, then adds a positive decimal exponent to that count. When the mantissa has a leading 0 (the .X / -0.X special case), shifting the decimal point past that zero annihilates it, but the code still keeps the counted digit and adds the exponent on top. The inflated integral-digit count makes seq -w pad output one character wider than the widest rendered value.

Static-analysis finding based on reading main; not executed here.

Location

  • File: src/uu/seq/src/numberparse.rs
  • Function: compute_num_digits
  • Relevant code:
let (mut int_digits, mut frac_digits) = match parts[0].find('.') {
    Some(i) => {
        // Cover special case .X and -.X where we behave as if there was a leading 0:
        // 0.X, -0.X.
        let int_digits = match i {
            0 => 1,
            1 if parts[0].starts_with('-') => 2,
            _ => i,
        };
        (int_digits, parts[0].len() - i - 1)
    }
    None => (parts[0].len(), 0),
};
...
if exp > 0 {
    int_digits += exp.try_into().unwrap_or(0);
}

Problem

For the operand "0.5e1" (value 5):

  1. find('.') -> i = 1; the special case yields int_digits = 1 (the counted leading 0) and frac_digits = 1.
  2. exp = 1 > 0 -> int_digits += 1 -> 2.
  3. But 0.5e1 == 5, whose rendered form "5" has exactly 1 integral digit: shifting the point past the leading zero removes that zero (05e1 would be 5, not 05). The correct count is therefore max(1, i + exp - 1)-style logic that first cancels the pad digit; instead both are summed.
  4. Downstream, seq -w 0.5e1 0.5e1 derives padding = max(integral digits) = 2 and prints 05.

The file's own tests encode the same over-count (e.g. -0.1e2 expected to contribute width 4, while -10 renders as 3 characters).

Trigger / Reproduction

Based on source reading:

$ seq -w 0.5e1 0.5e1
05          # actual
5           # expected

Same class: .5e2, -0.1e2, any operand whose mantissa starts with 0/. and whose exponent is at least the number of fractional digits.

Expected Behavior

The equal-width padding should equal the width of the widest actually-rendered value among all sequence elements - here 5, so no padding.

Actual Behavior

Padding is computed from an inflated digit count that double-counts the special-cased leading zero once the exponent moves past it, producing spurious leading zeros / overwide fields.

Impact

Wrong textual output for documented input syntax (scientific notation is accepted by seq's parser). Cosmetic rather than data-destructive, but deterministic and visible in scripts relying on fixed-width columns.

Suggested Direction

When i corresponds to the leading-zero special case, subtract the consumed pad digit from int_digits before applying a positive exponent (or compute the integer-part width from the normalized value mantissa * 10^exp directly).

Evidence

  • Lines 60-66 explicitly count the synthesized leading zero as an integral digit.
  • Lines 77-78 add the exponent unconditionally for exp > 0; there is no interaction between the two adjustments anywhere in the function.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions