Skip to content

Scientific format near int32.max exponent produces un-parseable display exponent #185

@thedavidmeister

Description

@thedavidmeister

Context

toDecimalString(float, scientific: true) of a Float with exponent near int32.max produces a scientific string whose display exponent exceeds int32.max. The parser's final packLossless step then rejects the value with ExponentOverflow. Round-trip fails.

Root cause

Scientific format renders coef × 10^exp as d.dddd × 10^displayExp where displayExp = exp + 75 or exp + 76 (after maximizeFull in _toScientific plus the scale factor). For exp within ~76 of int32.max, displayExp exceeds int32.max, and the parser's pack step cannot accept it because the exponent must fit int32.

Reproduction

int256 coef = 1521128172876797514106627746310;  // 31-digit int224 value
int32 exp = type(int32).max;  // 2147483647
Float f = LibDecimalFloat.packLossless(coef, exp);
string memory s = LibFormatDecimalFloat.toDecimalString(f, true);
// s ≈ "1.521128...e2147483677"   (displayExp = exp + 30 + 46 after maximize/scale)
(bytes4 err, Float parsed) = LibParseDecimalFloat.parseDecimalFloat(s);
// Reverts: ExponentOverflow(1.521e29, 2147483648)

The failure was found by testFormatParseRoundTripScientificFullDomain during #182 fuzz development; the scientific fuzz had to bound exponent ∈ [int32.min + 80, int32.max - 80] to avoid it.

Condition

Triggers for any Float where exp + scaleExponent > int32.max. With scaleExponent ∈ {75, 76}, this means exp > int32.max - 76. Symmetric case on the negative side: exp < int32.min + 76 risks display exponent below int32.min.

Fix directions

Unlike the non-scientific case, the parser cannot accept these values even in principle — the Float type itself uses int32 for the exponent, and displayExp > int32.max is not representable. So the fix must be formatter-side:

  1. Revert in the formatter: _toScientific should detect when exp + scaleExponent overflows int32 and revert with UnformatableExponent(exp). Honest behavior: "this Float cannot be rendered in scientific form."
  2. Reduce the mantissa precision: instead of always maximizing the coefficient to 75-76 digits, use a smaller scale when the exponent is extreme, so displayExp stays within int32. Preserves round-trip but loses some precision.

(1) is simpler and matches existing UnformatableExponent semantics for non-scientific.

Related

  • Symmetric issue: non-scientific format with positive exponent produces strings too long for the parser to accumulate (separate issue).
  • Fuzz coverage today: testFormatParseRoundTripScientificFullDomain fuzzes coefficient in full int224 range, exponent in [int32.min + 80, int32.max - 80]. The near-boundary exponent ranges are blocked on this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions