Skip to content

Honour AllowParentheses, AllowTrailingSign and AllowCurrencySymbol in Parse [patch] - #184

Open
matt-edmondson wants to merge 2 commits into
mainfrom
fix/152-parentheses-trailing-sign-currency
Open

matt-edmondson wants to merge 2 commits into
mainfrom
fix/152-parentheses-trailing-sign-currency

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Summary

Parse(span, style, provider) accepted these three styles, and Any and Currency include them, but it never read them. So "(1.5)", "1.5-", "¤1.5" and "$1,234.50" all threw FormatException. Generic code that calls INumberBase<T>.Parse(s, NumberStyles.Currency, provider) failed only for PreciseNumber.

A new StripAffixes step runs before the existing digit loop, which is otherwise unchanged:

  • AllowParentheses: ( … ) around the number means negative. A sign inside the parentheses is rejected.
  • AllowTrailingSign: a negative or positive sign after the digits, including after an exponent (1.5E2-). The ASCII hyphen is accepted, as it already is for a leading sign.
  • AllowCurrencySymbol: CurrencySymbol before or after the number. The sign and the symbol can come in either order, as in -$1.5, $-1.5 and 1,5 €-. Once a symbol has been read, CurrencyDecimalSeparator and CurrencyGroupSeparator replace the number separators.

At most one sign and one symbol are read. Under a style that doesn't allow a construct, it is still rejected as a non-digit.

One deliberate difference from decimal: a space before or after the symbol may be U+0020, U+00A0 or U+202F. Windows' de-DE and fr-FR write 1.234,50 € with a no-break space. decimal takes that only from a culture whose own separators use U+00A0. This repo already lets a typed space stand in for a no-break-space group separator, so this follows the same rule. TestParseAcceptsANoBreakSpaceBeforeACurrencySymbol pins it.

Tests

PreciseNumberParseAffixTests adds:

  • Accepted inputs: every row from the issue (plus $-…, -… € and … €-) parses to what decimal.Parse returns for the same text, style and format, through both Parse and TryParse. The formats are hand-built (Dollars, Euros), like PreciseNumberParseCultureTests, so the cases don't depend on ICU data.
  • Rejected inputs: each one is asserted to fail in decimal first, so the expectation is the BCL's. They are:
    • a style that doesn't allow the construct
    • (-1.5)
    • -1.5-
    • $1.5$
    • ()
    • a lone $
    • a lone -

16 of the accept cases fail on main. The reject cases pass both before and after; they guard against accepting too much. Full suite: 573 passed, 0 failed.

Note

Open PR #182 also edits Parse, in the white-space trimming just above this change. The two touch neighbouring lines, so whichever lands second may need a small rebase.

Fixes #152


Generated by Claude Code

matt-edmondson and others added 2 commits November 1, 2026 04:29
… Parse [patch]

Parse took these styles, and Any and Currency include them, but never read
them, so "(1.5)", "1.5-" and "$1,234.50" threw. Strip the parentheses, a
trailing sign and a leading or trailing currency symbol before the digits,
and read the currency separators once a symbol is present.

Fixes #152

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW4sAxuwubJA58cavro
SonarCloud S3776 counted its cognitive complexity at 27 against a limit of
15. Behaviour is unchanged.

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

sonarqubecloud Bot commented Nov 1, 2026

Copy link
Copy Markdown

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

Labels

None yet

Projects

None yet

1 participant