fix: normalize extreme exponents in non-scientific decimal formatter#180
Closed
fix: normalize extreme exponents in non-scientific decimal formatter#180
Conversation
The non-scientific path in `toDecimalString` reverted with `UnformatableExponent` when the exponent was outside [-76, 76], because `10 ** uint256(abs(exponent))` overflows uint256 at that range. However, the library's own arithmetic operations (add, sub) can produce Floats with exponents outside this range through normal operations — particularly catastrophic cancellation when subtracting nearly-equal values. Fix: instead of reverting, truncate trailing coefficient digits to bring the exponent within the formattable range. This loses insignificant precision at the 10^-77+ scale but produces a valid decimal string for any Float the arithmetic can create. This was discovered in production: a market-making bot accumulated 605 position events via Float add/sub, producing a net position with exponent -77 that crashed on serialization.
Contributor
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Problem
The non-scientific path in
LibFormatDecimalFloat.toDecimalStringreverts withUnformatableExponentwhen the exponent is outside[-76, 76], because10 ** uint256(abs(exponent))overflowsuint256at that range.However, the library's own arithmetic operations (
add,sub) can produce Floats with exponents outside this range through normal operations. Specifically, catastrophic cancellation when subtracting nearly-equal maximized values:add/subcallmaximizeFullon both inputs, pushing coefficients to ~76 digits with very negative exponents (e.g., -76)packLossystores this as-is (small coefficient fits inint224)Discovery
This was found in production: a market-making bot (st0x.liquidity) accumulated 605 position events via Float
add/sub. The net position ended up with exponent-77, which crashed the application during serialization withUnformatableExponent.Fix
Instead of reverting when the exponent is outside
[-76, 76], truncate trailing coefficient digits by dividing by10^(abs(exponent) - 76)and clamping the exponent to the boundary. This loses insignificant trailing precision (at the10^-77+scale) but produces a valid decimal string for any Float the arithmetic can create.Testing
UnformatableExponentreverts — they now verify successful formatting with truncationcoefficient=9999999910959448, exponent=-77exponent=-78(two digits of truncation)testFormatDecimalRoundTripNonNegative,testFormatDecimalRoundTripNegative) pass with 5096 runs each