Summary
seq -f %a / seq --format=%a (hexadecimal-float conversion) aborts with an out-of-memory error when an argument has a large-magnitude decimal exponent, even though the value is perfectly valid and GNU formats it without issue.
The crash is in formatting, not parsing — uutils parses the value fine (other conversions work), but the %a formatter builds a binary big-integer whose size is proportional to the exponent magnitude. A huge exponent makes it try to allocate exabytes, so the allocator fails and the process aborts (exit 134).
Steps to reproduce
$ seq -f %a 1E-1000000000000000000 1
memory allocation of 375000000000000024 bytes failed
Aborted (core dumped)
$ echo $?
134
--format=%a behaves identically. (1E-1000000000000000000 is a valid, tiny number — effectively 0.) The size that fails scales with the exponent magnitude, so a larger exponent fails larger (e.g. 1E-2000000000000000000 → 750000000000000024 bytes failed).
Expected behavior
GNU seq parses and formats the same value without crashing — it emulates a long double (≈15-bit exponent), so it never builds an oversized representation:
$ seq -f %a 1E-1000000000000000000 1
0x0p+0
0x8p-3
$ echo $?
0
Root cause
format_float_hexadecimal (in uucore/src/lib/features/format/num_format.rs) converts frac10 * 10^exp10 into frac2 * 2^exp2 by shifting a num-bigint value left by a margin derived from the exponent:
let exp10 = -p; // p = the value's decimal exponent
// ...
// Negative exponent: shift left by `margin`, then divide by 5^-exp10
let margin =
((max_precision + 1) as i64 * 4 - frac10.bits() as i64).max(0) + -exp10 * 3 + 1;
(
(frac10 << margin) / 5.to_bigint().unwrap().pow(-exp10 as u32), // <- allocates ~margin bits
exp10 - margin,
)
margin grows linearly with -exp10 and is never bounded. For exp10 = -10¹⁸, margin ≈ 3 × 10¹⁸ bits, so frac10 << margin asks num-bigint to build an integer of that many bits. The shift allocates a digit buffer sized to margin / 64 64-bit words — the failing allocation is Vec::with_capacity inside num-bigint:
num_format.rs:627 (frac10 << margin) / ...
-> num-bigint-0.4.6/src/biguint/shift.rs:12 biguint_shl (digits = margin / 64)
-> num-bigint-0.4.6/src/biguint/shift.rs:30 Vec::with_capacity(len) <-- OOM here
margin / 64 ≈ 4.7 × 10¹⁶ words × 8 bytes ≈ 3.75 × 10¹⁷ bytes — exactly the 375000000000000024 bytes the allocator reports before aborting. The function's own comment already flags the hazard:
TODO: this is most accurate, but frac2 will grow a lot for large precision or
exponent, and formatting will get very slow.
GNU caps the exponent to what a long double can represent, so it formats the value cheaply (0x0p+0) instead of materializing a giant bignum.
Summary
seq -f %a/seq --format=%a(hexadecimal-float conversion) aborts with an out-of-memory error when an argument has a large-magnitude decimal exponent, even though the value is perfectly valid and GNU formats it without issue.The crash is in formatting, not parsing — uutils parses the value fine (other conversions work), but the
%aformatter builds a binary big-integer whose size is proportional to the exponent magnitude. A huge exponent makes it try to allocate exabytes, so the allocator fails and the process aborts (exit 134).Steps to reproduce
--format=%abehaves identically. (1E-1000000000000000000is a valid, tiny number — effectively0.) The size that fails scales with the exponent magnitude, so a larger exponent fails larger (e.g.1E-2000000000000000000→750000000000000024 bytes failed).Expected behavior
GNU
seqparses and formats the same value without crashing — it emulates along double(≈15-bit exponent), so it never builds an oversized representation:Root cause
format_float_hexadecimal(inuucore/src/lib/features/format/num_format.rs) convertsfrac10 * 10^exp10intofrac2 * 2^exp2by shifting anum-bigintvalue left by amarginderived from the exponent:margingrows linearly with-exp10and is never bounded. Forexp10 = -10¹⁸,margin ≈ 3 × 10¹⁸bits, sofrac10 << marginasksnum-bigintto build an integer of that many bits. The shift allocates a digit buffer sized tomargin / 6464-bit words — the failing allocation isVec::with_capacityinsidenum-bigint:margin / 64 ≈ 4.7 × 10¹⁶words × 8 bytes ≈3.75 × 10¹⁷bytes — exactly the375000000000000024 bytesthe allocator reports before aborting. The function's own comment already flags the hazard:GNU caps the exponent to what a
long doublecan represent, so it formats the value cheaply (0x0p+0) instead of materializing a giant bignum.