Skip to content

Commit eaef3eb

Browse files
Merge pull request #165 from ktsu-dev/fix/132-honour-number-styles
Honour AllowDecimalPoint, AllowExponent and AllowLeadingSign in Parse [patch]
2 parents 496373e + 5273188 commit eaef3eb

2 files changed

Lines changed: 66 additions & 5 deletions

File tree

‎PreciseNumber.Test/PreciseNumberParseCultureTests.cs‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,54 @@ public void Parse_ReadsTheExponentSignWithTheProvider()
100100
{
101101
Assert.AreEqual(P("0.015"), PreciseNumber.Parse("1,5e−2", CommaDecimal));
102102
}
103+
104+
[TestMethod]
105+
[DataRow("1.5", NumberStyles.Integer)]
106+
[DataRow("1e5", NumberStyles.Integer)]
107+
[DataRow("-5", NumberStyles.None)]
108+
[DataRow("1e 5", NumberStyles.Float)]
109+
[DataRow("1e5", NumberStyles.Number)]
110+
[DataRow("1.5", NumberStyles.None)]
111+
[DataRow("1.5", NumberStyles.Float)]
112+
[DataRow("-1.5e-3", NumberStyles.Float)]
113+
[DataRow("-1.5e-3", NumberStyles.Any)]
114+
[DataRow("1,234.5", NumberStyles.Any)]
115+
[DataRow("-5", NumberStyles.Integer)]
116+
[DataRow("1e+5", NumberStyles.Float)]
117+
public void TryParse_AcceptsWhatDecimalAccepts(string text, NumberStyles style)
118+
{
119+
bool decimalParsed = decimal.TryParse(text, style, CultureInfo.InvariantCulture, out decimal expected);
120+
bool parsed = PreciseNumber.TryParse(text, style, CultureInfo.InvariantCulture, out PreciseNumber actual);
121+
122+
Assert.AreEqual(decimalParsed, parsed, $"PreciseNumber and decimal disagree on whether \"{text}\" parses under {style}.");
123+
if (parsed)
124+
{
125+
Assert.AreEqual(P(expected.ToString(CultureInfo.InvariantCulture)), actual);
126+
}
127+
}
128+
129+
[TestMethod]
130+
public void Parse_ThrowsForWhatTheStyleDoesNotAllow()
131+
{
132+
Assert.ThrowsExactly<FormatException>(() => PreciseNumber.Parse("1.5", NumberStyles.Integer, CultureInfo.InvariantCulture));
133+
Assert.ThrowsExactly<FormatException>(() => PreciseNumber.Parse("1e5", NumberStyles.Integer, CultureInfo.InvariantCulture));
134+
Assert.ThrowsExactly<FormatException>(() => PreciseNumber.Parse("-5", NumberStyles.None, CultureInfo.InvariantCulture));
135+
Assert.ThrowsExactly<FormatException>(() => PreciseNumber.Parse("1e 5", NumberStyles.Float, CultureInfo.InvariantCulture));
136+
}
137+
138+
[TestMethod]
139+
public void Parse_WithoutAStyleStillReadsFractionsExponentsAndSigns()
140+
{
141+
Assert.AreEqual(P("1.5"), PreciseNumber.Parse("1.5", CultureInfo.InvariantCulture));
142+
Assert.AreEqual(P("-0.0015"), PreciseNumber.Parse("-1.5e-3", CultureInfo.InvariantCulture));
143+
Assert.AreEqual(P("1234.5"), PreciseNumber.Parse("1,234.5", CultureInfo.InvariantCulture));
144+
}
145+
146+
[TestMethod]
147+
public void Parse_ReadsACulturesNegativeSignOnlyWhenTheStyleAllowsIt()
148+
{
149+
Assert.AreEqual(P("-2.5"), PreciseNumber.Parse("−2,5", NumberStyles.Float, CommaDecimal));
150+
Assert.IsFalse(PreciseNumber.TryParse("−2", NumberStyles.None, CommaDecimal, out _));
151+
Assert.IsFalse(PreciseNumber.TryParse("-2", NumberStyles.None, CommaDecimal, out _));
152+
}
103153
}

‎PreciseNumber/PreciseNumber.cs‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,6 +1210,8 @@ public static PreciseNumber Parse(ReadOnlySpan<char> s, NumberStyles style, IFor
12101210
ReadOnlySpan<char> decimalSeparator = numberFormat.NumberDecimalSeparator;
12111211
ReadOnlySpan<char> groupSeparator = numberFormat.NumberGroupSeparator;
12121212
bool allowThousands = style.HasFlag(NumberStyles.AllowThousands) && !groupSeparator.IsEmpty;
1213+
bool allowDecimalPoint = style.HasFlag(NumberStyles.AllowDecimalPoint);
1214+
bool allowExponent = style.HasFlag(NumberStyles.AllowExponent);
12131215

12141216
int startIndex = ReadLeadingSign(s, style, numberFormat, out bool isNegative);
12151217

@@ -1229,7 +1231,7 @@ public static PreciseNumber Parse(ReadOnlySpan<char> s, NumberStyles style, IFor
12291231
for (int i = startIndex; i < s.Length; i++)
12301232
{
12311233
char c = s[i];
1232-
if (s[i..].StartsWith(decimalSeparator, StringComparison.Ordinal))
1234+
if (allowDecimalPoint && s[i..].StartsWith(decimalSeparator, StringComparison.Ordinal))
12331235
{
12341236
if (hasDecimal)
12351237
{
@@ -1248,10 +1250,12 @@ public static PreciseNumber Parse(ReadOnlySpan<char> s, NumberStyles style, IFor
12481250
continue;
12491251
}
12501252

1251-
if (c is 'e' or 'E')
1253+
if (allowExponent && c is 'e' or 'E')
12521254
{
12531255
// An exponent outside the range of int throws OverflowException, which TryParse reports as failure.
1254-
exponent = int.Parse(s[(i + 1)..], NumberStyles.Integer, numberFormat);
1256+
// Only a sign may precede its digits: NumberStyles.Integer would also let white space through,
1257+
// accepting "1e 5" where every BCL numeric type rejects it.
1258+
exponent = int.Parse(s[(i + 1)..], NumberStyles.AllowLeadingSign, numberFormat);
12551259
break;
12561260
}
12571261

@@ -1313,10 +1317,17 @@ private static ReadOnlySpan<char> TrimAllowedWhite(ReadOnlySpan<char> s, NumberS
13131317
/// Reads a leading sign spelled as <paramref name="numberFormat"/> spells it, returning the index
13141318
/// of the first character after it. The ASCII hyphen stays accepted whatever the culture spells
13151319
/// its negative sign with, as it always has been here and as the BCL does for cultures that use
1316-
/// U+2212. A positive sign is only read when <paramref name="style"/> allows a leading sign.
1320+
/// U+2212. Neither sign is read unless <paramref name="style"/> allows a leading sign, so under
1321+
/// <see cref="NumberStyles.None"/> a sign is left in place and rejected as a non-digit.
13171322
/// </summary>
13181323
private static int ReadLeadingSign(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo numberFormat, out bool isNegative)
13191324
{
1325+
isNegative = false;
1326+
if (!style.HasFlag(NumberStyles.AllowLeadingSign))
1327+
{
1328+
return 0;
1329+
}
1330+
13201331
isNegative = true;
13211332
if (s.StartsWith(numberFormat.NegativeSign, StringComparison.Ordinal))
13221333
{
@@ -1329,7 +1340,7 @@ private static int ReadLeadingSign(ReadOnlySpan<char> s, NumberStyles style, Num
13291340
}
13301341

13311342
isNegative = false;
1332-
return style.HasFlag(NumberStyles.AllowLeadingSign) && s.StartsWith(numberFormat.PositiveSign, StringComparison.Ordinal)
1343+
return s.StartsWith(numberFormat.PositiveSign, StringComparison.Ordinal)
13331344
? numberFormat.PositiveSign.Length
13341345
: 0;
13351346
}

0 commit comments

Comments
 (0)