Skip to content

Read the number separators as well as the currency ones under AllowCurrencySymbol [patch] - #198

Merged
matt-edmondson merged 1 commit into
mainfrom
fix/parse-currency-number-separators
Nov 10, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
fix/parse-currency-number-separators

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Parse used CurrencyDecimalSeparator and CurrencyGroupSeparator only after it had stripped a currency symbol. Otherwise it used the number separators.

The BCL does this differently whenever the style includes AllowCurrencySymbol, which NumberStyles.Any does, and Any is what the default Parse(s, provider) uses:

  • it tries the currency separators first;
  • it also accepts the number separators, unless a symbol came ahead of the digits.

Some cultures write money with different separators from other numbers, such as en-DE, fr-CH and de-AT. There, PreciseNumber disagreed with decimal:

  • Parse("1.25", en-DE) returned 125;
  • Parse("1.5", fr-CH) threw.

Change

  • A new ChooseSeparators picks a primary pair and a fallback pair. The primary pair is the currency separators when the style allows a symbol, and the number separators otherwise. The fallback pair is the number separators, used only when the style allows a symbol and none was read ahead of the digits.
  • A new MatchSeparator tries the primary separator, then the fallback. The decimal separator is still tried before the group separator, and a group separator may still only follow integral digits.
  • StripAffixes now reports whether the symbol was leading. A trailing symbol is read after the digits in the BCL too.

Tests

  • TestParseAcceptsWhatDecimalAccepts gains two hand-built formats whose currency and number separators differ:
    • Mixed, shaped like en-DE;
    • Swiss, shaped like fr-CH.
  • It gains rows for "1.5", "1.25", "1 234.5", "1,5 €", "1 234,50 CHF", "CHF 1 234.50" and "€1,5" under Any and Currency.
  • TestParseRejectsWhatDecimalRejects gains "CHF 1,5": after a leading symbol, only the currency separators are read.
  • TestDefaultParseReadsCurrencySeparatorsAsDecimalDoes covers the Parse(s, provider) and TryParse(s, provider, out) overloads. It uses hand-built formats, so it does not depend on ICU and runs in globalization-invariant mode.
  • Without the fix, 14 of the new cases fail.

One existing test fixture changed. PreciseNumberParseCultureTests.CommaDecimal is "shaped like de-DE", but it left the currency separators at the invariant . and ,. Under Any, decimal.TryParse("1.234.567,5", NumberStyles.Any, CommaDecimal) returns false, so Parse_HonoursLeadingSignWhitespaceAndThousandsUnderNumberStylesAny was pinning behaviour the BCL doesn't have. The fixture now sets the currency separators de-DE actually uses (, and .), and the test still checks thousands grouping under Any.

The full suite passes: 604 tests. I did not run benchmarks: the change adds at most one extra separator comparison per character, and only under currency styles.

Note: #194 also edits Parse's digit loop, so whichever lands second may need a small rebase.

Fixes #195


Generated by Claude Code

…rrencySymbol [patch]

Parse used the currency separators only once a symbol had been stripped, and
the number separators otherwise. The BCL reads the currency separators first
whenever the style allows a symbol, and the number separators too unless a
symbol came ahead of the digits. For a culture that writes money with other
separators than its numbers (en-DE, fr-CH, de-AT), Parse("1.25", en-DE)
returned 125 and Parse("1.5", fr-CH) threw, where decimal reads 1.25 and 1.5.

Fixes #195

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

sonarqubecloud Bot commented Nov 9, 2026

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit 65059c7 into main Nov 10, 2026
15 checks passed
@matt-edmondson
matt-edmondson deleted the fix/parse-currency-number-separators branch November 10, 2026 23:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants