Context
toDecimalString(float, scientific: false) with positive exponent emits digits(coefficient) + exponent trailing-zero characters — a total output length of digits(coefficient) + exponent characters. The parser (parseDecimalFloat) accumulates each character into an int256 coefficient, which overflows around 77 digits regardless of the actual numeric value. Round-trip fails.
Reproduction
Bounded to within the formatter's cap so format itself succeeds, but parse overflows:
// Coefficient is 56 digits; exponent is within MAX_NON_SCIENTIFIC_EXPONENT (1000).
int256 coef = -36252536662599755883997874465192896811584344295850018217;
int32 exp = 900; // any value where digits(coef) + exp > ~76
Float f = LibDecimalFloat.packLossless(coef, exp);
string memory s = LibFormatDecimalFloat.toDecimalString(f, false);
// s is a ~956-character decimal string: the 56 digits + 900 zeros.
(bytes4 err, Float parsed) = LibParseDecimalFloat.parseDecimalFloat(s);
// err != 0 — parse rejects the string as the coefficient accumulator overflows.
The failure was found by testFormatParseRoundTripScientificFullDomain/testFormatParseRoundTripNonScientificNegExpFullDomain during #182 fuzz development; the non-scientific fuzz had to bound exponent ≤ 0 to avoid it.
Condition
Triggers whenever digits(coefficient) + exponent > ~76 in non-scientific mode with exponent > 0. With coefficient up to int224 (~68 digits), any exponent > ~8 combined with a non-trivial coefficient can trigger it.
Fix directions
- Narrow the formatter: have
_toNonScientific revert with UnformatableExponent when the projected output length exceeds what the parser can accept. Symmetric with the current MAX_NON_SCIENTIFIC_EXPONENT cap but tighter in the positive direction.
- Widen the parser: parse incrementally and collapse trailing zeros into the exponent rather than building one big int256 coefficient. e.g., once the accumulator has ≥ 68 digits, start counting subsequent zeros as exponent adjustments rather than coefficient digits.
(2) is the more useful long-term fix; (1) is a quick correctness patch.
Related
- Symmetric issue: scientific format near
int32.max exponent produces display exponents above int32.max (separate issue).
- Fuzz coverage today:
testFormatParseRoundTripNonScientificNegExpFullDomain fuzzes exponent ∈ [-1000, 0] comprehensively. Full non-scientific domain coverage is blocked on this issue.
Context
toDecimalString(float, scientific: false)with positiveexponentemitsdigits(coefficient) + exponenttrailing-zero characters — a total output length ofdigits(coefficient) + exponentcharacters. The parser (parseDecimalFloat) accumulates each character into anint256coefficient, which overflows around 77 digits regardless of the actual numeric value. Round-trip fails.Reproduction
Bounded to within the formatter's cap so format itself succeeds, but parse overflows:
The failure was found by
testFormatParseRoundTripScientificFullDomain/testFormatParseRoundTripNonScientificNegExpFullDomainduring #182 fuzz development; the non-scientific fuzz had to boundexponent ≤ 0to avoid it.Condition
Triggers whenever
digits(coefficient) + exponent > ~76in non-scientific mode withexponent > 0. Withcoefficientup to int224 (~68 digits), anyexponent > ~8combined with a non-trivial coefficient can trigger it.Fix directions
_toNonScientificrevert withUnformatableExponentwhen the projected output length exceeds what the parser can accept. Symmetric with the currentMAX_NON_SCIENTIFIC_EXPONENTcap but tighter in the positive direction.(2) is the more useful long-term fix; (1) is a quick correctness patch.
Related
int32.maxexponent produces display exponents aboveint32.max(separate issue).testFormatParseRoundTripNonScientificNegExpFullDomainfuzzesexponent ∈ [-1000, 0]comprehensively. Full non-scientific domain coverage is blocked on this issue.