Skip to content

TryParsePartial under-reports charsConsumed when the input has leading whitespace #132261

Closed

Description

@pawlos

Description

INumberBase<>.TryParsePartial returns the correct value but a charsConsumed count that omits any leading whitespace, for every culture that does not use exactly "+" and "-" as its positive and negative signs.

96 of the 786 cultures on my machine are affected, including sv-SE, fi-FI, nb-NO, et-EE, lt-LT and eu-ES as well as the Arabic, Persian and Hebrew locales.

It also requires the style to permit both leading whitespace and a leading sign, as NumberStyles.Integer does. With AllowLeadingWhite alone the count is correct even in an affected culture, because the sign-parsing block is never entered.

Reproduction Steps

This is the example from the .NET 11 release notes for this API, continued the way its own CSV motivation implies - skip the delimiter, parse the next field - and run under two cultures:

using System.Globalization;

foreach (var (name, culture) in new[] {
    ("invariant", CultureInfo.InvariantCulture), ("sv-SE", new CultureInfo("sv-SE")) })
{
    Console.WriteLine($"--- {name} ---");
    ReadOnlySpan<char> input = "123; 456";     // the release-notes input, verbatim
    int field = 0;
    while (input.Length > 0 && field < 5)
    {
        if (!int.TryParsePartial(input, NumberStyles.Integer, culture, out int value, out int consumed))
        {
            input = input[1..];                // not a number here: skip the delimiter
            continue;
        }
        Console.WriteLine($"    field {field++}: value={value} consumed={consumed} remaining=\"{input[consumed..]}\"");
        if (consumed == 0) break;
        input = input[consumed..];
    }
}

Output:

--- invariant ---
    field 0: value=123 consumed=3 remaining="; 456"
    field 1: value=456 consumed=4 remaining=""

--- sv-SE ---
    field 0: value=123 consumed=3 remaining="; 456"
    field 1: value=456 consumed=3 remaining="6"
    field 2: value=6 consumed=1 remaining=""

"123; 456" holds two fields. Under sv-SE the reader produces three: the digits left behind by the short count are read as a further field.

The first field parses correctly because it has no leading whitespace - which is why the release-notes snippet looks right as written. The discrepancy appears on the second field, " 456", where the space following the delimiter is consumed but not counted. Whitespace after a delimiter is ordinary in CSV.

A single value shows the same thing more directly:

int.TryParsePartial(" 5", NumberStyles.Integer, new CultureInfo("sv-SE"), out int v, out int consumed);
// v = 5, consumed = 1 - expected 2

Expected behavior

charsConsumed counts every character the call consumed, so that text[..charsConsumed] is exactly the text that was parsed and text[charsConsumed..] is exactly what remains. This is what happens under the invariant culture.

Actual behavior

The leading whitespace is not counted. The value itself is scanned correctly - only the count is short - so a caller advancing by charsConsumed rewinds into the number it has already read and scans that tail a second time, reporting those digits as a further value.

Regression?

No. TryParsePartial is new in .NET 11.

Known Workarounds

Omit NumberStyles.AllowLeadingWhite or trim the input before calling.

Configuration

.NET SDK 11.0.100-preview.7.26381.103, runtime 11.0.0-preview.7.26381.103, linux-x64, windows-x64

Other information

Observed on different numeric types - not only int type.

Found by differential fuzzing.

Activity

  1. dotnet-policy-service commented on Aug 13, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @dotnet/area-system-numerics
    See info in area-owners.md if you want to be subscribed.

  2. added a commit that references this issue on Aug 13, 2026
    57fe570
  3. locked and limited conversation to collaborators on Sep 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions