Skip to content

Commit 348dd6a

Browse files
authored
Add fixes to both the Decimal and DecimalView impls to allow exponents to follow dots (#374)
* Add fixes to DecimalView to allow exponents to follow dots * Finalize fixes, add tests
1 parent 3f14886 commit 348dd6a

File tree

2 files changed

+112
-23
lines changed

2 files changed

+112
-23
lines changed

source/mir/bignum/decimal.d

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,8 @@ struct Decimal(size_t maxSize64)
274274
{
275275
bool recentUnderscore;
276276
}
277+
// Was there a recent character within the set: ['.', 'e', 'E', 'd', 'D']?
278+
bool recentModifier;
277279
static if (!allowLeadingZeros)
278280
{
279281
if (d == 0)
@@ -299,6 +301,7 @@ struct Decimal(size_t maxSize64)
299301
return false;
300302
key = DecimalExponentKey.dot;
301303
dot = true;
304+
recentModifier = true;
302305
goto F;
303306
}
304307
}
@@ -323,6 +326,7 @@ struct Decimal(size_t maxSize64)
323326
{
324327
recentUnderscore = false;
325328
}
329+
recentModifier = false;
326330
{
327331
import mir.checkedint: mulu;
328332
bool overflow;
@@ -342,31 +346,35 @@ struct Decimal(size_t maxSize64)
342346
coefficient.length = v != 0;
343347
static if (allowUnderscores)
344348
{
345-
return !recentUnderscore;
349+
return !recentUnderscore && !recentModifier;
346350
}
347351
else
348352
{
349-
return true;
353+
return !recentModifier;
350354
}
351355
}
352-
static if (allowUnderscores)
353-
{
354-
if (recentUnderscore)
355-
return false;
356-
}
356+
357357
switch (d)
358358
{
359359
case DecimalExponentKey.dot:
360+
// The dot modifier CANNOT be preceded by any modifiers.
361+
if (recentModifier || key != DecimalExponentKey.none)
362+
break;
363+
364+
static if (allowUnderscores)
365+
{
366+
// If we allow underscores, the dot also CANNOT be preceded by any underscores.
367+
// It must be preceded by a number.
368+
if (recentUnderscore)
369+
break;
370+
}
360371
key = DecimalExponentKey.dot;
361372
if (_expect(dot, false))
362373
break;
363374
dot = true;
364375
if (str.length)
365376
{
366-
static if (allowUnderscores)
367-
{
368-
recentUnderscore = true;
369-
}
377+
recentModifier = true;
370378
continue;
371379
}
372380
static if (allowDotOnBounds)
@@ -388,14 +396,24 @@ struct Decimal(size_t maxSize64)
388396
case DecimalExponentKey.e:
389397
case DecimalExponentKey.E:
390398
import mir.parse: parse;
399+
// We don't really care if the e/E/d/D modifiers are preceded by a modifier,
400+
// so as long as they are a dot or a regular number.
401+
if (key != DecimalExponentKey.dot && key != DecimalExponentKey.none)
402+
break;
391403
key = cast(DecimalExponentKey)d;
392-
if (parse(str, exponent) && str.length == 0)
404+
if (parse(str, exponent) && str.length == 0) {
405+
recentModifier = false;
393406
goto E;
407+
}
394408
break;
395409
}
396410
static if (allowUnderscores)
397411
{
398412
case '_' - '0':
413+
// A underscore cannot be preceded by an underscore or a modifier.
414+
if (recentUnderscore || recentModifier)
415+
break;
416+
399417
recentUnderscore = true;
400418
if (str.length)
401419
continue;
@@ -554,6 +572,52 @@ struct Decimal(size_t maxSize64)
554572
assert(key == DecimalExponentKey.E);
555573
assert(cast(double) decimal == 0);
556574

575+
/++ Test that Issue #365 is handled properly +/
576+
assert(decimal.fromStringImpl("123456.e0", key));
577+
assert(key == DecimalExponentKey.e);
578+
assert(cast(double) decimal == 123_456.0);
579+
580+
assert(decimal.fromStringImpl("123_456.e0", key));
581+
assert(key == DecimalExponentKey.e);
582+
assert(cast(double) decimal == 123_456.0);
583+
584+
assert(decimal.fromStringImpl("123456.E0", key));
585+
assert(key == DecimalExponentKey.E);
586+
assert(cast(double) decimal == 123_456.0);
587+
588+
assert(decimal.fromStringImpl("123_456.E0", key));
589+
assert(key == DecimalExponentKey.E);
590+
assert(cast(double) decimal == 123_456.0);
591+
592+
assert(decimal.fromStringImpl("123456.d0", key));
593+
assert(key == DecimalExponentKey.d);
594+
assert(cast(double) decimal == 123_456.0);
595+
596+
assert(decimal.fromStringImpl("123_456.d0", key));
597+
assert(key == DecimalExponentKey.d);
598+
assert(cast(double) decimal == 123_456.0);
599+
600+
assert(decimal.fromStringImpl("123456.D0", key));
601+
assert(key == DecimalExponentKey.D);
602+
assert(cast(double) decimal == 123_456.0);
603+
604+
assert(decimal.fromStringImpl("123_456.D0", key));
605+
assert(key == DecimalExponentKey.D);
606+
assert(cast(double) decimal == 123_456.0);
607+
608+
/++ Test invalid examples with the fix introduced for Issue #365 +/
609+
assert(!decimal.fromStringImpl("123_456_.D0", key));
610+
assert(!decimal.fromStringImpl("123_456.DD0", key));
611+
assert(!decimal.fromStringImpl("123_456_.E0", key));
612+
assert(!decimal.fromStringImpl("123_456.EE0", key));
613+
assert(!decimal.fromStringImpl("123456.ED0", key));
614+
assert(!decimal.fromStringImpl("123456E0D0", key));
615+
assert(!decimal.fromStringImpl("123456._D0", key));
616+
assert(!decimal.fromStringImpl("123456_.D0", key));
617+
assert(!decimal.fromStringImpl("123456.E0D0", key));
618+
assert(!decimal.fromStringImpl("123456.D0_", key));
619+
assert(!decimal.fromStringImpl("123456_", key));
620+
557621
foreach (str; ["-nan", "-NaN", "-NAN"])
558622
{
559623
assert(decimal.fromStringImpl(str, key));

source/mir/bignum/low_level_view.d

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2489,6 +2489,7 @@ struct DecimalView(W, WordEndian endian = TargetEndian, Exp = sizediff_t)
24892489
scope @trusted pure @nogc nothrow
24902490
if (isSomeChar!C)
24912491
{
2492+
debug import std.stdio;
24922493
import mir.utility: _expect;
24932494

24942495
version(LDC)
@@ -2538,6 +2539,9 @@ struct DecimalView(W, WordEndian endian = TargetEndian, Exp = sizediff_t)
25382539
bool recentUnderscore;
25392540
}
25402541

2542+
// Was there a recent character within the set: ['.', 'e', 'E', 'd', 'D']?
2543+
bool recentModifier;
2544+
25412545
static if (!allowLeadingZeros)
25422546
{
25432547
if (d == 0)
@@ -2566,6 +2570,7 @@ struct DecimalView(W, WordEndian endian = TargetEndian, Exp = sizediff_t)
25662570
return false;
25672571
key = DecimalExponentKey.dot;
25682572
dot = true;
2573+
recentModifier = true;
25692574
goto F;
25702575
}
25712576
}
@@ -2591,6 +2596,7 @@ struct DecimalView(W, WordEndian endian = TargetEndian, Exp = sizediff_t)
25912596
{
25922597
recentUnderscore = false;
25932598
}
2599+
recentModifier = false;
25942600
v *= 10;
25952601
S:
25962602
t *= 10;
@@ -2622,35 +2628,44 @@ struct DecimalView(W, WordEndian endian = TargetEndian, Exp = sizediff_t)
26222628
coefficient = work.mostSignificant == 0 ? coefficient.init : work;
26232629
static if (allowUnderscores)
26242630
{
2625-
return !recentUnderscore;
2631+
// If we have no characters, then we should return true IF
2632+
// the last character was NOT a underscore OR a modifier
2633+
return !recentUnderscore && !recentModifier;
26262634
}
26272635
else
26282636
{
2629-
return true;
2637+
// If we don't allow underscores, and we have no characters,
2638+
// then we should return true IF the character was NOT a modifier.
2639+
return !recentModifier;
26302640
}
26312641
}
26322642
}
26332643

26342644
continue;
26352645
}
2636-
static if (allowUnderscores)
2637-
{
2638-
if (recentUnderscore)
2639-
return false;
2640-
}
2646+
26412647
switch (d)
26422648
{
26432649
case DecimalExponentKey.dot:
2650+
// The dot modifier CANNOT be preceded by any modifiers.
2651+
if (recentModifier || key != DecimalExponentKey.none)
2652+
return false;
2653+
2654+
static if (allowUnderscores)
2655+
{
2656+
// If we allow underscores, the dot also CANNOT be preceded by any underscores.
2657+
// It must be preceded by a number.
2658+
if (recentUnderscore)
2659+
return false;
2660+
}
2661+
26442662
key = DecimalExponentKey.dot;
26452663
if (_expect(dot, false))
26462664
break;
26472665
dot = true;
26482666
if (str.length)
26492667
{
2650-
static if (allowUnderscores)
2651-
{
2652-
recentUnderscore = true;
2653-
}
2668+
recentModifier = true;
26542669
continue;
26552670
}
26562671
static if (allowDotOnBounds)
@@ -2672,9 +2687,14 @@ struct DecimalView(W, WordEndian endian = TargetEndian, Exp = sizediff_t)
26722687
case DecimalExponentKey.e:
26732688
case DecimalExponentKey.E:
26742689
import mir.parse: parse;
2690+
// We don't really care if the e/E/d/D modifiers are preceded by a modifier,
2691+
// so as long as they are a dot or a regular number.
2692+
if (key != DecimalExponentKey.dot && key != DecimalExponentKey.none)
2693+
return false;
26752694
key = cast(DecimalExponentKey)d;
26762695
if (parse(str, exponent) && str.length == 0)
26772696
{
2697+
recentModifier = false;
26782698
if (t != 1)
26792699
goto L;
26802700
goto M;
@@ -2684,6 +2704,10 @@ struct DecimalView(W, WordEndian endian = TargetEndian, Exp = sizediff_t)
26842704
static if (allowUnderscores)
26852705
{
26862706
case '_' - '0':
2707+
// A underscore cannot be preceded by an underscore or a modifier.
2708+
if (recentUnderscore || recentModifier)
2709+
return false;
2710+
26872711
recentUnderscore = true;
26882712
if (str.length)
26892713
continue;
@@ -2693,6 +2717,7 @@ struct DecimalView(W, WordEndian endian = TargetEndian, Exp = sizediff_t)
26932717
}
26942718
break;
26952719
}
2720+
26962721
return false;
26972722

26982723
static if (allowSpecialValues)

0 commit comments

Comments
 (0)