Skip to content

Commit e51ce35

Browse files
committed
add leading zeros and underscores support for Decimal parsing
1 parent a6232a6 commit e51ce35

File tree

3 files changed

+118
-28
lines changed

3 files changed

+118
-28
lines changed

source/mir/bignum/decimal.d

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ struct Decimal(size_t maxSize64)
119119
Returns: false in case of overflow or incorrect string.
120120
Precondition: non-empty coefficients.
121121
+/
122-
bool fromStringImpl(C, bool allowSpecialValues = true, bool allowDotOnBounds = true, bool allowDExponent = true, bool allowStartingPlus = true, bool checkEmpty = true)(scope const(C)[] str, out DecimalExponentKey key)
122+
bool fromStringImpl(C, bool allowSpecialValues = true, bool allowDotOnBounds = true, bool allowDExponent = true, bool allowStartingPlus = true, bool checkEmpty = true, bool allowUnderscores = true, bool allowLeadingZeros = true)(scope const(C)[] str, out DecimalExponentKey key)
123123
@safe pure @nogc nothrow
124124
if (isSomeChar!C)
125125
{
@@ -163,14 +163,20 @@ struct Decimal(size_t maxSize64)
163163
ulong v;
164164
uint afterDot;
165165
bool dot;
166-
167-
if (d == 0)
166+
static if (allowUnderscores)
168167
{
169-
if (str.length == 0)
170-
goto R;
171-
if (str[0] >= '0' && str[0] <= '9')
172-
return false;
173-
goto S;
168+
bool recentUnderscore;
169+
}
170+
static if (!allowLeadingZeros)
171+
{
172+
if (d == 0)
173+
{
174+
if (str.length == 0)
175+
goto R;
176+
if (str[0] >= '0' && str[0] <= '9')
177+
return false;
178+
goto S;
179+
}
174180
}
175181

176182
if (d < 10)
@@ -206,6 +212,10 @@ struct Decimal(size_t maxSize64)
206212

207213
if (_expect(d <= 10, true))
208214
{
215+
static if (allowUnderscores)
216+
{
217+
recentUnderscore = false;
218+
}
209219
{
210220
import core.checkedint: addu, mulu;
211221
bool overflow;
@@ -223,18 +233,35 @@ struct Decimal(size_t maxSize64)
223233
R:
224234
coefficient.data[0] = v;
225235
coefficient.length = v != 0;
226-
return true;
236+
static if (allowUnderscores)
237+
{
238+
return !recentUnderscore;
239+
}
240+
else
241+
{
242+
return true;
243+
}
244+
}
245+
key = cast(DecimalExponentKey)d;
246+
static if (allowUnderscores)
247+
{
248+
if (recentUnderscore)
249+
return false;
227250
}
228251
switch (d)
229252
{
230-
D:
231253
case DecimalExponentKey.dot:
232-
if (dot)
254+
if (_expect(dot, false))
233255
break;
234-
key = cast(DecimalExponentKey)d;
235256
dot = true;
236257
if (str.length)
258+
{
259+
static if (allowUnderscores)
260+
{
261+
recentUnderscore = true;
262+
}
237263
continue;
264+
}
238265
static if (allowDotOnBounds)
239266
{
240267
goto R;
@@ -251,11 +278,18 @@ struct Decimal(size_t maxSize64)
251278
}
252279
case DecimalExponentKey.e:
253280
case DecimalExponentKey.E:
254-
key = cast(DecimalExponentKey)d;
255281
import mir.parse: parse;
256282
if (parse(str, exponent) && str.length == 0)
257283
goto E;
258284
break;
285+
static if (allowUnderscores)
286+
{
287+
case '_' - '0':
288+
recentUnderscore = true;
289+
if (str.length)
290+
continue;
291+
break;
292+
}
259293
default:
260294
}
261295
break;
@@ -287,7 +321,7 @@ struct Decimal(size_t maxSize64)
287321
{
288322
import mir.bignum.low_level_view: DecimalView, BigUIntView, MaxWordPow10;
289323
auto work = DecimalView!size_t(false, 0, BigUIntView!size_t(coefficient.data));
290-
auto ret = work.fromStringImpl!(C, allowSpecialValues, allowDExponent, allowStartingPlus)(str, key);
324+
auto ret = work.fromStringImpl!(C, allowSpecialValues, allowDExponent, allowStartingPlus, checkEmpty, allowUnderscores, allowLeadingZeros)(str, key);
291325
coefficient.length = cast(uint) work.coefficient.coefficients.length;
292326
coefficient.sign = work.sign;
293327
exponent = work.exponent;
@@ -359,13 +393,13 @@ struct Decimal(size_t maxSize64)
359393
assert(decimal.fromStringImpl("4.", key));
360394
assert(!decimal.fromStringImpl(".", key));
361395
assert(decimal.fromStringImpl("0.", key));
362-
assert(!decimal.fromStringImpl("00", key));
396+
assert(decimal.fromStringImpl("00", key));
363397
assert(!decimal.fromStringImpl("0d", key));
364398
}
365399

366400
static if (maxSize64 == 3)
367401
version(mir_bignum_test)
368-
// @safe pure nothrow @nogc
402+
@safe pure nothrow @nogc
369403
unittest
370404
{
371405
import mir.conv: to;
@@ -424,7 +458,7 @@ struct Decimal(size_t maxSize64)
424458
assert(decimal.fromStringImpl("4.", key));
425459
assert(!decimal.fromStringImpl(".", key));
426460
assert(decimal.fromStringImpl("0.", key));
427-
assert(!decimal.fromStringImpl("00", key));
461+
assert(decimal.fromStringImpl("00", key));
428462
assert(!decimal.fromStringImpl("0d", key));
429463
}
430464

source/mir/bignum/low_level_view.d

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2048,7 +2048,7 @@ struct DecimalView(W, WordEndian endian = TargetEndian, Exp = sizediff_t)
20482048
Precondition: non-empty coefficients
20492049
Note: doesn't support signs.
20502050
+/
2051-
bool fromStringImpl(C, bool allowSpecialValues = true, bool allowDotOnBounds = true, bool allowDExponent = true, bool allowStartingPlus = true, bool checkEmpty = true)(scope const(C)[] str, out DecimalExponentKey key)
2051+
bool fromStringImpl(C, bool allowSpecialValues = true, bool allowDotOnBounds = true, bool allowDExponent = true, bool allowStartingPlus = true, bool checkEmpty = true, bool allowUnderscores = true, bool allowLeadingZeros = true)(scope const(C)[] str, out DecimalExponentKey key)
20522052
@safe pure @nogc nothrow
20532053
if (isSomeChar!C)
20542054
{
@@ -2097,16 +2097,24 @@ struct DecimalView(W, WordEndian endian = TargetEndian, Exp = sizediff_t)
20972097
uint afterDot;
20982098
bool dot;
20992099

2100-
if (d == 0)
2100+
static if (allowUnderscores)
21012101
{
2102-
if (str.length == 0)
2102+
bool recentUnderscore;
2103+
}
2104+
2105+
static if (!allowLeadingZeros)
2106+
{
2107+
if (d == 0)
21032108
{
2104-
coefficient = coefficient.init;
2105-
return true;
2109+
if (str.length == 0)
2110+
{
2111+
coefficient = coefficient.init;
2112+
return true;
2113+
}
2114+
if (str[0] >= '0' && str[0] <= '9')
2115+
return false;
2116+
goto S;
21062117
}
2107-
if (str[0] >= '0' && str[0] <= '9')
2108-
return false;
2109-
goto S;
21102118
}
21112119

21122120
if (d < 10)
@@ -2143,6 +2151,10 @@ struct DecimalView(W, WordEndian endian = TargetEndian, Exp = sizediff_t)
21432151

21442152
if (_expect(d <= 10, true))
21452153
{
2154+
static if (allowUnderscores)
2155+
{
2156+
recentUnderscore = false;
2157+
}
21462158
v *= 10;
21472159
S:
21482160
t *= 10;
@@ -2172,22 +2184,39 @@ struct DecimalView(W, WordEndian endian = TargetEndian, Exp = sizediff_t)
21722184
M:
21732185
exponent -= afterDot;
21742186
coefficient = work.mostSignificant == 0 ? coefficient.init : work;
2175-
return true;
2187+
static if (allowUnderscores)
2188+
{
2189+
return !recentUnderscore;
2190+
}
2191+
else
2192+
{
2193+
return true;
2194+
}
21762195
}
21772196
}
21782197

21792198
continue;
21802199
}
21812200
key = cast(DecimalExponentKey)d;
2201+
static if (allowUnderscores)
2202+
{
2203+
if (recentUnderscore)
2204+
return false;
2205+
}
21822206
switch (d)
21832207
{
2184-
D:
21852208
case DecimalExponentKey.dot:
21862209
if (_expect(dot, false))
21872210
break;
21882211
dot = true;
21892212
if (str.length)
2213+
{
2214+
static if (allowUnderscores)
2215+
{
2216+
recentUnderscore = true;
2217+
}
21902218
continue;
2219+
}
21912220
static if (allowDotOnBounds)
21922221
{
21932222
goto L;
@@ -2212,6 +2241,14 @@ struct DecimalView(W, WordEndian endian = TargetEndian, Exp = sizediff_t)
22122241
goto M;
22132242
}
22142243
break;
2244+
static if (allowUnderscores)
2245+
{
2246+
case '_' - '0':
2247+
recentUnderscore = true;
2248+
if (str.length)
2249+
continue;
2250+
break;
2251+
}
22152252
default:
22162253
}
22172254
break;

source/mir/parse.d

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,16 @@ version(mir_bignum_test)
9696
assert("-12.3e-30".fromString!double == -12.3e-30);
9797
assert("2.9802322387695312E-8".fromString!double == 2.9802322387695312E-8);
9898

99+
// default support of underscores
100+
assert("123_456.789_012".fromString!double == 123_456.789_012);
101+
assert("12_34_56_78_90_12e-6".fromString!double == 123_456.789_012);
102+
103+
// default support of leading zeros
104+
assert("010".fromString!double == 10.0);
105+
assert("000010".fromString!double == 10.0);
106+
assert("0000.10".fromString!double == 0.1);
107+
assert("0000e10".fromString!double == 0);
108+
99109
/// Test CTFE support
100110
static assert("-12.3e-30".fromString!double == -0x1.f2f280b2414d5p-97);
101111
static assert("+12.3e+30".fromString!double == 0x1.367ee3119d2bap+103);
@@ -120,7 +130,16 @@ version(mir_bignum_test)
120130
@safe pure unittest
121131
{
122132
import std.exception: assertThrown;
123-
assertThrown("010".fromString!float);
133+
assertThrown("1_".fromString!float);
134+
assertThrown("1__2".fromString!float);
135+
assertThrown("_1".fromString!float);
136+
assertThrown("123_.456".fromString!float);
137+
assertThrown("123_e0".fromString!float);
138+
assertThrown("123._456".fromString!float);
139+
assertThrown("12__34.56".fromString!float);
140+
assertThrown("123.456_".fromString!float);
141+
assertThrown("-_123.456".fromString!float);
142+
assertThrown("_123.456".fromString!float);
124143
}
125144

126145
/++

0 commit comments

Comments
 (0)