Skip to content

Commit ea2facd

Browse files
committed
Add ParseDecimal for StringSegment
1 parent bddea5a commit ea2facd

2 files changed

Lines changed: 236 additions & 4 deletions

File tree

src/ServiceStack.Text/Support/StringSegmentExtensions.cs

Lines changed: 205 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ enum ParseState
108108
LeadingWhite,
109109
Sign,
110110
Number,
111+
DecimalPoint,
112+
FractionNumber,
113+
Exponent,
114+
ExponentSign,
115+
ExponentValue,
111116
TrailingWhite
112117
}
113118

@@ -125,6 +130,9 @@ enum ParseState
125130

126131
private static ulong ParseUnsignedInteger(StringSegment value, ulong maxValue)
127132
{
133+
if (value.Length == 0)
134+
throw new FormatException("Input string was not in a correct format");
135+
128136
ulong result = 0;
129137
int i = 0;
130138
var state = ParseState.LeadingWhite;
@@ -194,9 +202,11 @@ private static ulong ParseUnsignedInteger(StringSegment value, ulong maxValue)
194202
return result;
195203
}
196204

197-
198205
private static long ParseSignedInteger(StringSegment value, long maxValue, long minValue)
199206
{
207+
if (value.Length == 0)
208+
throw new FormatException("Input string was not in a correct format");
209+
200210
long result = 0;
201211
int i = 0;
202212
var state = ParseState.LeadingWhite;
@@ -215,7 +225,7 @@ private static long ParseSignedInteger(StringSegment value, long maxValue, long
215225
} else if (c == '-')
216226
{
217227
negative = true;
218-
state = ParseState.Number;
228+
state = ParseState.Sign;
219229
i++;
220230
} else if ( c == '0')
221231
{
@@ -231,6 +241,21 @@ private static long ParseSignedInteger(StringSegment value, long maxValue, long
231241
throw new FormatException("Input string was not in a correct format");
232242
}
233243
break;
244+
case ParseState.Sign:
245+
if (c == '0')
246+
{
247+
state = ParseState.TrailingWhite;
248+
i++;
249+
} else if (c > '0' && c <= '9')
250+
{
251+
result = - (c - '0');
252+
state = ParseState.Number;
253+
i++;
254+
} else
255+
{
256+
throw new FormatException("Input string was not in a correct format");
257+
}
258+
break;
234259
case ParseState.Number:
235260
if (c >= '0' && c <= '9')
236261
{
@@ -278,5 +303,183 @@ private static long ParseSignedInteger(StringSegment value, long maxValue, long
278303
return result;
279304
}
280305

306+
public static decimal ParseDecimal(this StringSegment value, bool allowThousands = false)
307+
{
308+
if (value.Length == 0)
309+
throw new FormatException("Input string was not in a correct format");
310+
311+
decimal result = 0;
312+
int i = 0;
313+
var state = ParseState.LeadingWhite;
314+
bool negative = false;
315+
bool noIntegerPart = false;
316+
decimal fraction = 0.1m;
317+
318+
while (i < value.Length)
319+
{
320+
var c = value.GetChar(i);
321+
322+
switch (state)
323+
{
324+
case ParseState.LeadingWhite:
325+
if (Char.IsWhiteSpace(c))
326+
{
327+
i++;
328+
}
329+
else if (c == '-')
330+
{
331+
negative = true;
332+
state = ParseState.Sign;
333+
i++;
334+
} else if (c == '.')
335+
{
336+
noIntegerPart = true;
337+
state = ParseState.FractionNumber;
338+
i++;
339+
340+
if (i == value.Length)
341+
{
342+
throw new FormatException("Input string was not in a correct format");
343+
}
344+
}
345+
else if (c == '0')
346+
{
347+
state = ParseState.DecimalPoint;
348+
i++;
349+
}
350+
else if (c > '0' && c <= '9')
351+
{
352+
result = (c - '0');
353+
state = ParseState.Number;
354+
i++;
355+
}
356+
else
357+
{
358+
throw new FormatException("Input string was not in a correct format");
359+
}
360+
break;
361+
case ParseState.Sign:
362+
if (c == '.')
363+
{
364+
noIntegerPart = true;
365+
state = ParseState.FractionNumber;
366+
i++;
367+
368+
if (i == value.Length)
369+
{
370+
throw new FormatException("Input string was not in a correct format");
371+
}
372+
} else if (c == '0')
373+
{
374+
state = ParseState.DecimalPoint;
375+
i++;
376+
}
377+
else if (c > '0' && c <= '9')
378+
{
379+
result = -(c - '0');
380+
state = ParseState.Number;
381+
i++;
382+
}
383+
else
384+
{
385+
throw new FormatException("Input string was not in a correct format");
386+
}
387+
break;
388+
case ParseState.Number:
389+
if (c == '.')
390+
{
391+
state = ParseState.FractionNumber;
392+
i++;
393+
} else if (c >= '0' && c <= '9')
394+
{
395+
checked
396+
{
397+
result = negative
398+
? 10 * result - (c - '0')
399+
: 10 * result + (c - '0');
400+
}
401+
i++;
402+
}
403+
else if (Char.IsWhiteSpace(c))
404+
{
405+
state = ParseState.TrailingWhite;
406+
i++;
407+
}
408+
else
409+
{
410+
throw new FormatException("Input string was not in a correct format");
411+
}
412+
break;
413+
case ParseState.DecimalPoint:
414+
if (c == '.')
415+
{
416+
state = ParseState.FractionNumber;
417+
i++;
418+
} else
419+
{
420+
throw new FormatException("Input string was not in a correct format");
421+
}
422+
break;
423+
case ParseState.FractionNumber:
424+
if (Char.IsWhiteSpace(c))
425+
{
426+
if (noIntegerPart)
427+
throw new FormatException("Input string was not in a correct format");
428+
state = ParseState.TrailingWhite;
429+
i++;
430+
} else if (c == 'e' || c == 'E')
431+
{
432+
if (noIntegerPart && fraction == 0.1m)
433+
throw new FormatException("Input string was not in a correct format");
434+
state = ParseState.Exponent;
435+
i++;
436+
} else if (c >= '0' && c <= '9')
437+
{
438+
checked
439+
{
440+
result = negative
441+
? result - fraction * (c - '0')
442+
: result + fraction * (c - '0');
443+
fraction *= 0.1m;
444+
}
445+
i++;
446+
} else
447+
{
448+
throw new FormatException("Input string was not in a correct format");
449+
}
450+
break;
451+
case ParseState.Exponent:
452+
if (c == '-' || (c >= '0' && c <= '9'))
453+
{
454+
int exp = value.Subsegment(i, value.Length - i).ParseInt16();
455+
checked
456+
{
457+
result *= (decimal)Math.Pow(10.0, exp);
458+
}
459+
460+
//set i to end of string, because ParseInt16 eats number and all trailing whites
461+
i = value.Length;
462+
} else
463+
{
464+
throw new FormatException("Input string was not in a correct format");
465+
}
466+
break;
467+
case ParseState.TrailingWhite:
468+
if (Char.IsWhiteSpace(c))
469+
{
470+
state = ParseState.TrailingWhite;
471+
i++;
472+
}
473+
else
474+
{
475+
throw new FormatException("Input string was not in a correct format");
476+
}
477+
break;
478+
}
479+
}
480+
481+
return result;
482+
}
483+
281484
}
282485
}

tests/ServiceStack.Text.Tests/Support/StringSegmentParse.cs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
namespace ServiceStack.Text.Tests.Support
99
{
1010
[TestFixture]
11-
public class StringSegmentParse
11+
public class StringSegmentParseTests
1212
{
1313
[Test]
1414
public void Can_parse_int32()
1515
{
1616
Assert.That(new StringSegment("0").ParseInt32(), Is.EqualTo(0));
17+
Assert.That(new StringSegment("-0").ParseInt32(), Is.EqualTo(0));
1718
Assert.That(new StringSegment("1").ParseInt32(), Is.EqualTo(1));
1819
Assert.That(new StringSegment(int.MaxValue.ToString()).ParseInt32(), Is.EqualTo(int.MaxValue));
1920
Assert.That(new StringSegment(int.MinValue.ToString()).ParseInt32(), Is.EqualTo(int.MinValue));
@@ -23,6 +24,9 @@ public void Can_parse_int32()
2324
Assert.That(new StringSegment("234 ").ParseInt32(), Is.EqualTo(234));
2425
Assert.That(new StringSegment(" 234").ParseInt32(), Is.EqualTo(234));
2526
Assert.That(new StringSegment(" -234 ").ParseInt32(), Is.EqualTo(-234));
27+
Assert.Throws<FormatException>(() => new StringSegment("").ParseInt32());
28+
Assert.Throws<FormatException>(() => new StringSegment("01").ParseInt32());
29+
Assert.Throws<FormatException>(() => new StringSegment("-01").ParseInt32());
2630
Assert.Throws<FormatException>(() => new StringSegment(" - 234 ").ParseInt32());
2731
Assert.Throws<FormatException>(() => new StringSegment(" 2.34 ").ParseInt32());
2832
Assert.Throws<OverflowException>(() => new StringSegment("12345678901234567890").ParseInt32());
@@ -31,5 +35,30 @@ public void Can_parse_int32()
3135
Assert.Throws<FormatException>(() => new StringSegment(" 1234 123").ParseInt32());
3236
}
3337

38+
[Test]
39+
public void Can_parse_decimal()
40+
{
41+
Assert.Throws<FormatException>(() => new StringSegment(".").ParseDecimal());
42+
Assert.Throws<FormatException>(() => new StringSegment("").ParseDecimal());
43+
Assert.That(new StringSegment("0").ParseDecimal(), Is.EqualTo(0));
44+
Assert.That(new StringSegment("-0").ParseDecimal(), Is.EqualTo(0));
45+
Assert.That(new StringSegment("0.").ParseDecimal(), Is.EqualTo(0));
46+
Assert.That(new StringSegment("-0.").ParseDecimal(), Is.EqualTo(0));
47+
Assert.That(new StringSegment(".1").ParseDecimal(), Is.EqualTo(.1m));
48+
Assert.That(new StringSegment("-.1").ParseDecimal(), Is.EqualTo(-.1m));
49+
Assert.That(new StringSegment("10.001").ParseDecimal(), Is.EqualTo(10.001m));
50+
Assert.That(new StringSegment(" 10.001").ParseDecimal(), Is.EqualTo(10.001m));
51+
Assert.That(new StringSegment("10.001 ").ParseDecimal(), Is.EqualTo(10.001m));
52+
Assert.That(new StringSegment(" 10.001 ").ParseDecimal(), Is.EqualTo(10.001m));
53+
Assert.That(new StringSegment("-10.001").ParseDecimal(), Is.EqualTo(-10.001m));
54+
//exponent
55+
Assert.That(new StringSegment("10.001E3").ParseDecimal(), Is.EqualTo(10001m));
56+
Assert.That(new StringSegment(".001e5").ParseDecimal(), Is.EqualTo(100m));
57+
Assert.That(new StringSegment("10.001E-2").ParseDecimal(), Is.EqualTo(0.10001m));
58+
Assert.That(new StringSegment("10.001e-8").ParseDecimal(), Is.EqualTo(0.00000010001m));
59+
Assert.That(new StringSegment("2.e2").ParseDecimal(), Is.EqualTo(200m));
60+
Assert.Throws<FormatException>(() => new StringSegment(".e2").ParseDecimal());
61+
62+
}
63+
}
3464
}
35-
}

0 commit comments

Comments
 (0)