Skip to content

Support the F, N, E, R and G<n> standard format strings [patch] - #170

Merged
matt-edmondson merged 2 commits into
mainfrom
fix/134-standard-format-strings
Oct 12, 2026
Merged

matt-edmondson merged 2 commits into
mainfrom
fix/134-standard-format-strings

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #134

Problem

TryFormat, which every ToString(format, provider) goes through, threw a bare FormatException for any format other than empty or G. So $"{x:F2}", string.Format("{0:N2}", x), logging templates, and generic INumber<T> code that formats values all crashed at runtime.

Change

The new PreciseNumber/PreciseNumber.Formatting.cs renders the standard formats from the exact significand and exponent. Nothing goes through double, and rounding is half away from zero, the same as Round.

Format Behaviour Example (1234.5678)
F<n> Round(n), padded to exactly n fractional digits. Default n is the culture's NumberDecimalDigits F2 → 1234.57
N<n> As F, plus the culture's group sizes, group separator, and NumberNegativePattern N2 → 1,234.57
E<n> One digit, n fractional digits, a signed exponent of at least 3 digits. Default n is 6, and e gives a lowercase marker E5 → 1.23457E+003
R The exact general form, the same as G 1234.5678
G<n> ReduceSignificance(n), then the general form. G0 keeps every digit G5 → 1234.6
  • Unsupported formats: custom formats such as 0.00 or #,##0, C, X, R with a precision, and precisions above 999,999,999 throw a FormatException. Its message names the format and lists the supported ones. The issue allowed either this or a fallback to G. I chose the exception because a silent G fallback would hand back unrounded digits to someone who asked for two decimals.
  • Very small values: E writes 1.00E-1000000 directly, with no million-character fixed-point expansion.
  • Buffers: TryFormat returns false when the destination is too small for a standard format. ToString takes these formats straight to the renderer, so it never comes back empty because its buffer guess was too small.
  • Size limit: a rendering longer than a string can hold still throws OverflowException, as the existing G path does.

README.md documents the supported formats.

Tests

PreciseNumberTests gets new and updated cases:

  • TestToStringStandardFormats has 25 data rows covering F/N/E/R/G<n>, default precisions, carries (999.995 → 1,000.00, 9.9999 → 1.00E+001), zero, small negatives, and lowercase e.
  • An interpolated {x:F2} and string.Format("{0:N2}").
  • Custom separators, group sizes [3, 2], and negative pattern 0. These use a cloned NumberFormatInfo, because the test project runs in invariant-globalization mode.
  • E2 at an exponent of -1,000,000.
  • TryFormat returning false when the destination is too small.
  • Descriptive FormatExceptions for unsupported formats.
  • TestToStringVariousFormats and TestTryFormatVariousFormats previously asserted the old throw. They now assert the rendered text.

Verification:

  • With the library change reverted, 39 tests fail. With it applied, all 512 pass on net10.0.
  • The library builds clean on net7.0 through net10.0.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BPNa479nM4FXr2G22kBHmy


Generated by Claude Code

ToString and TryFormat threw a bare FormatException for every format
other than empty or "G", so $"{x:F2}", "N2" and logging templates
crashed at runtime.

Fixed point (F), number (N, with the culture's group sizes, separator
and negative pattern) and scientific (E) now render from the exact
digits, rounding half away from zero like Round. R is the exact general
form, and G<n> reduces to n significant digits first. Any other format,
including custom ones such as "0.00", throws a FormatException whose
message names the supported formats.

Fixes #134

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BPNa479nM4FXr2G22kBHmy
Addresses the SonarCloud findings on this change: FormatFixedPoint's
cognitive complexity (integral digits and the negative pattern now live
in their own helpers) and MSTEST0046 in the new tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BPNa479nM4FXr2G22kBHmy
@sonarqubecloud

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit ef07dea into main Oct 12, 2026
14 checks passed
@matt-edmondson
matt-edmondson deleted the fix/134-standard-format-strings branch October 12, 2026 23:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ToString/TryFormat throw FormatException for every format except "G": $"{x:F2}", "N2" and "0.00" all crash

2 participants