Skip to content

Formatting a value with an extreme exponent throws or produces a huge string #75

Description

@matt-edmondson

Problem

ToString and TryFormat always write fixed-point notation, including every zero the exponent implies. Parse accepts any exponent that fits in an int, so a value can parse successfully and then fail to format, or format as an enormous string. Observed on 2.0.3:

Value ToString() TryFormat into 64 chars
1E-2147483648 throws OverflowException throws ArgumentOutOfRangeException
1E2147483647 throws ArgumentOutOfRangeException throws ArgumentOutOfRangeException
1E-1000000 1,000,002 characters returns false
1E1000000 1,000,001 characters returns false

TryFormat should return false when the destination is too small rather than throw, as it does for the last two rows.

Cause

The formatting paths do their length arithmetic in int and negate or take the absolute value of the exponent:

  • ToString sizes its buffer as SignificantDigits + int.Abs(Exponent) + ... (PreciseNumber.cs lines 334 to 338). int.Abs throws for int.MinValue, and the sum overflows for exponents near int.MaxValue.
  • CountDecimalDigits returns int.Abs(Exponent) (PreciseNumber.cs line 657).
  • TryFormat computes fractionalDigits = -Exponent (PreciseNumber.cs line 1133), which wraps for int.MinValue, and derives slice lengths from the exponent.

#72 fixed this pattern in the conversion paths with range checks and by widening to long, but left formatting alone.

Suggested fix

  1. Do the length arithmetic in long. TryFormat then returns false for any length that doesn't fit, and ToString fails with a clear exception when the text can't exist as a string.
  2. Consider scientific notation for extreme exponents. Supporting the "E" and "G" standard format strings, or switching the default past some exponent the way double does, would make these values printable at all. Check how the format argument is handled today before choosing.
  3. Add tests for int.MinValue and int.MaxValue exponents across ToString, TryFormat, and CountDecimalDigits.

Repro

#:package ktsu.PreciseNumber@2.0.3

using System.Globalization;
using ktsu.PreciseNumber;

PreciseNumber tiny = PreciseNumber.Parse("1E-2147483648", NumberStyles.Float, CultureInfo.InvariantCulture);
Console.WriteLine(tiny.ToString(CultureInfo.InvariantCulture)); // OverflowException

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

P3Real gap or design debt; scheduled workbugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions