fix: do the formatting length arithmetic in long - #77
Merged
Merged
Conversation
ToString and TryFormat sized their buffers with int arithmetic over the exponent, so a value whose exponent sat near either end of the int range threw rather than formatted: int.Abs(int.MinValue) overflows, and the sums near int.MaxValue wrapped negative and reached ArrayPool.Rent and Span.Slice as invalid lengths. Parse accepts any exponent an int holds, so a value could parse and then fail to format. Widen the lengths to long, the way #72 did for the conversion paths. TryFormat now answers false for a destination that cannot hold the rendering, whatever the exponent, and ToString throws an OverflowException naming the exponent when the text could not exist as a string at all. CountDecimalDigits returns long for the same reason: an exponent of int.MinValue implies one more decimal digit than an int can count. Round follows it, and stops raising ten to a power wider than the significand, since dropping one digit more than it holds always leaves zero. Formatting stays fixed point. Scientific notation for extreme exponents is a separate API decision and is left to the issue. Text benchmarks are unchanged within noise across all three digit counts, with allocations byte for byte identical. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XbHTU3pZh6egvtowTyuqNM
|
This was referenced Sep 16, 2026
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.



Fixes #75
Problem
ToStringandTryFormatsized their buffers withintarithmetic over the exponent, so a value whose exponent sat near either end of theintrange threw instead of formatting.Parseaccepts any exponent anintholds, so a value could parse successfully and then fail to format.ToString()beforeToString()afterTryFormatinto 64 chars before1E-2147483648OverflowException(fromint.Abs)OverflowExceptionnaming the exponentArgumentOutOfRangeExceptionfalse1E2147483647ArgumentOutOfRangeExceptionOverflowExceptionnaming the exponentArgumentOutOfRangeExceptionfalseChanges
ToStringsizes its buffer inlong, and throws anOverflowExceptionnaming the exponent when the fixed point text could not exist as a string at all, rather than surfacing the failure as a negation or allocation error.TryWriteDigits(behindTryFormat) does every length inlong, so the comparison against the destination's length is what answers. It now returnsfalsefor any destination too small, whatever the exponent, instead of wrapping a length negative and reachingSpan.Slice.CountDecimalDigitsreturnslong. An exponent ofint.MinValueimplies 2,147,483,648 decimal digits, one more than anintcan count, andint.Abs(int.MinValue)throws.LowestDecimalDigitsfollows it.Roundfollows the widened count, and stops raising ten to a power wider than the significand: dropping one digit more than the significand holds always leaves zero, so a value far below the requested place now rounds to zero instead of throwing.This is the same treatment #72 gave the conversion paths, applied to formatting.
Deliberately not changed
Formatting stays fixed point, so
1E-1000000still renders as 1,000,002 characters. Switching to scientific notation past some exponent, or supporting the"E"format string, is a separate API decision —"G"is the only format accepted today and changing what it means would change existing output. Left to #75 to decide.Testing
Six tests added in
PreciseNumberTests.cs, coveringint.MinValueandint.MaxValueexponents acrossToString,TryFormat,CountDecimalDigitsandRound, plus the parse-then-format path from the issue's repro and a regression guard at large-but-representable exponents (1E±1000).mainand pass with the fix — verified by revertingPreciseNumber.csalone and re-running. The sixth is the regression guard, which passes either way.TextBenchmarksrun before and after (--job short, Digits 8/30/200): means unchanged within noise, allocations byte for byte identical.No public API signature changes; the behaviour changes are all in cases that previously threw.
🤖 Generated with Claude Code
https://claude.ai/code/session_01XbHTU3pZh6egvtowTyuqNM
Generated by Claude Code