You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Decimal precision check does work proportional to the text, not to what decimal holds #276
On main (1353260, unreleased), DecimalLiteral.Represents (src/CodoMetis.ValueRanges/Internals/DecimalLiteral.cs, from #166 for #115) sizes its work by the supplied text instead of by what decimal can hold:
The powers of ten (lines 71 and 80) take their exponent from the text, so a short exponent literal costs work that grows superlinearly with the exponent.
The exponent is read with int.Parse (line 53), which throws OverflowException where Parse documents FormatException.
Every entry point is affected: DecimalRange, RangeSet<DecimalRange>, DecimalSet, DecimalSet<T>, in a literal, JSON, TypeConverter and the EF Core JSON document.
Fix: normalize before any arithmetic (strip leading and trailing zeros, adjust the spelled scale) and refuse when the significant digits or the scale exceed what decimal can hold, so the buffer and the powers are bounded by a constant; read the exponent with TryParse. Review the sibling readers (FloatLiteral, IntegerLiteral, UuidLiteral, IsoTemporal) for the same pattern.
Another symptom (reported by the user):{0e-10000000} ran for more than five seconds before the process was killed. BigInteger.Pow expanded the exponent even though the value is zero. Measured on main1353260 through DecimalSet.TryParse on a 1 MB thread:
Element
Before
After
0e-10000000
still running after 5 s
9 ms, reads 0
1e-1000000
154 ms, allocates 15 MB
0 ms, refused
0e99999999999
OverflowException from Parse
reads 0 (9.1.0 read 0 too)
1e-99999999999
OverflowException from Parse
FormatException
0. + 1M zeros + 1
stack overflow, process killed
12 ms, refused
1. + 2M zeros
stack overflow, process killed
25 ms, reads 1
Fix on fix/276-decimal-literal-bounds:Represents reduces both sides to a significand with no trailing zero times a power of ten, then compares two integers. It counts significant digits before keeping any and refuses more than 29, the most a decimal mantissa can have. It refuses an exponent past ±28. That leaves one pass over the text plus a constant, with no stack buffer and no BigInteger. The exponent is read with TryParse. A differential run of the old and new check over 2.5 million random texts that decimal.Parse accepts found 0 mismatches.
Sibling readers: none has the pattern. FloatLiteral uses BigInteger.Pow only in Format, and there the exponent comes from the value's binary exponent (at most about 1,100), not from the text. Its parse side is double.Parse plus linear scans. IntegerLiteral stops at the first overflow, UuidLiteral uses a fixed 16-byte buffer, and IsoTemporal parses fixed two-digit slices.
Activity
CaffeinatedCoder commentedon Oct 19, 2026
Another symptom (reported by the user):
{0e-10000000}ran for more than five seconds before the process was killed.BigInteger.Powexpanded the exponent even though the value is zero. Measured onmain1353260 throughDecimalSet.TryParseon a 1 MB thread:0e-100000001e-10000000e99999999999OverflowExceptionfromParse1e-99999999999OverflowExceptionfromParseFormatException0.+ 1M zeros +11.+ 2M zerosFix on
fix/276-decimal-literal-bounds:Representsreduces both sides to a significand with no trailing zero times a power of ten, then compares two integers. It counts significant digits before keeping any and refuses more than 29, the most a decimal mantissa can have. It refuses an exponent past ±28. That leaves one pass over the text plus a constant, with no stack buffer and noBigInteger. The exponent is read withTryParse. A differential run of the old and new check over 2.5 million random texts thatdecimal.Parseaccepts found 0 mismatches.Sibling readers: none has the pattern.
FloatLiteralusesBigInteger.Powonly inFormat, and there the exponent comes from the value's binary exponent (at most about 1,100), not from the text. Its parse side isdouble.Parseplus linear scans.IntegerLiteralstops at the first overflow,UuidLiteraluses a fixed 16-byte buffer, andIsoTemporalparses fixed two-digit slices.