Created
January 4, 2017 20:55
-
-
Save JackStouffer/0cf93d3cc861d699b334e18ea67fb66c to your computer and use it in GitHub Desktop.
std.conv.parse!double auto decoding test
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import std.stdio; | |
import std.range; | |
import std.algorithm; | |
import std.traits; | |
import std.container; | |
import std.meta; | |
import std.conv; | |
import std.datetime; | |
Target parse1(Target, Source)(ref Source p) | |
if (isInputRange!Source && isSomeChar!(ElementType!Source) && !is(Source == enum) && | |
isFloatingPoint!Target && !is(Target == enum)) | |
{ | |
import std.ascii : isDigit, isAlpha, toLower, toUpper, isHexDigit; | |
import std.exception : enforce; | |
import core.stdc.math : HUGE_VAL; | |
static immutable real[14] negtab = | |
[ 1e-4096L,1e-2048L,1e-1024L,1e-512L,1e-256L,1e-128L,1e-64L,1e-32L, | |
1e-16L,1e-8L,1e-4L,1e-2L,1e-1L,1.0L ]; | |
static immutable real[13] postab = | |
[ 1e+4096L,1e+2048L,1e+1024L,1e+512L,1e+256L,1e+128L,1e+64L,1e+32L, | |
1e+16L,1e+8L,1e+4L,1e+2L,1e+1L ]; | |
ConvException bailOut()(string msg = null, string fn = __FILE__, size_t ln = __LINE__) | |
{ | |
if (msg == null) | |
msg = "Floating point conversion error"; | |
return new ConvException(text(msg, " for input \"", p, "\"."), fn, ln); | |
} | |
static if (isNarrowString!Source) | |
{ | |
import std.string : representation, assumeUTF; | |
auto source = p.representation; | |
} | |
else | |
{ | |
alias source = p; | |
} | |
enforce(!source.empty, bailOut()); | |
bool sign = false; | |
switch (source.front) | |
{ | |
case '-': | |
sign = true; | |
source.popFront(); | |
enforce(!source.empty, bailOut()); | |
if (toLower(source.front) == 'i') | |
goto case 'i'; | |
enforce(!source.empty, bailOut()); | |
break; | |
case '+': | |
source.popFront(); | |
enforce(!source.empty, bailOut()); | |
break; | |
case 'i': case 'I': | |
source.popFront(); | |
enforce(!source.empty, bailOut()); | |
if (toLower(source.front) == 'n') | |
{ | |
source.popFront(); | |
enforce(!source.empty, bailOut()); | |
if (toLower(source.front) == 'f') | |
{ | |
// 'inf' | |
source.popFront(); | |
static if (isNarrowString!Source) | |
p = source.assumeUTF; | |
return sign ? -Target.infinity : Target.infinity; | |
} | |
} | |
goto default; | |
default: {} | |
} | |
bool isHex = false; | |
bool startsWithZero = source.front == '0'; | |
if (startsWithZero) | |
{ | |
source.popFront(); | |
if (source.empty) | |
{ | |
static if (isNarrowString!Source) | |
p = source.assumeUTF; | |
return (sign) ? -0.0 : 0.0; | |
} | |
isHex = source.front == 'x' || source.front == 'X'; | |
} | |
real ldval = 0.0; | |
char dot = 0; /* if decimal point has been seen */ | |
int exp = 0; | |
long msdec = 0, lsdec = 0; | |
ulong msscale = 1; | |
if (isHex) | |
{ | |
int guard = 0; | |
int anydigits = 0; | |
uint ndigits = 0; | |
source.popFront(); | |
while (!source.empty) | |
{ | |
int i = source.front; | |
while (isHexDigit(i)) | |
{ | |
anydigits = 1; | |
i = isAlpha(i) ? ((i & ~0x20) - ('A' - 10)) : i - '0'; | |
if (ndigits < 16) | |
{ | |
msdec = msdec * 16 + i; | |
if (msdec) | |
ndigits++; | |
} | |
else if (ndigits == 16) | |
{ | |
while (msdec >= 0) | |
{ | |
exp--; | |
msdec <<= 1; | |
i <<= 1; | |
if (i & 0x10) | |
msdec |= 1; | |
} | |
guard = i << 4; | |
ndigits++; | |
exp += 4; | |
} | |
else | |
{ | |
guard |= i; | |
exp += 4; | |
} | |
exp -= dot; | |
source.popFront(); | |
if (source.empty) | |
break; | |
i = source.front; | |
if (i == '_') | |
{ | |
source.popFront(); | |
if (source.empty) | |
break; | |
i = source.front; | |
} | |
} | |
if (i == '.' && !dot) | |
{ | |
source.popFront(); | |
dot = 4; | |
} | |
else | |
break; | |
} | |
// Round up if (guard && (sticky || odd)) | |
if (guard & 0x80 && (guard & 0x7F || msdec & 1)) | |
{ | |
msdec++; | |
if (msdec == 0) // overflow | |
{ | |
msdec = 0x8000000000000000L; | |
exp++; | |
} | |
} | |
enforce(anydigits, bailOut()); | |
enforce(!source.empty && (source.front == 'p' || source.front == 'P'), | |
bailOut("Floating point parsing: exponent is required")); | |
char sexp; | |
int e; | |
sexp = 0; | |
source.popFront(); | |
if (!source.empty) | |
{ | |
switch (source.front) | |
{ | |
case '-': sexp++; | |
goto case; | |
case '+': source.popFront(); enforce(!source.empty, | |
new ConvException("Error converting input"~ | |
" to floating point")); | |
break; | |
default: {} | |
} | |
} | |
ndigits = 0; | |
e = 0; | |
while (!source.empty && isDigit(source.front)) | |
{ | |
if (e < 0x7FFFFFFF / 10 - 10) // prevent integer overflow | |
{ | |
e = e * 10 + source.front - '0'; | |
} | |
source.popFront(); | |
ndigits = 1; | |
} | |
exp += (sexp) ? -e : e; | |
enforce(ndigits, new ConvException("Error converting input"~ | |
" to floating point")); | |
static if (real.mant_dig == 64) | |
{ | |
if (msdec) | |
{ | |
int e2 = 0x3FFF + 63; | |
// left justify mantissa | |
while (msdec >= 0) | |
{ | |
msdec <<= 1; | |
e2--; | |
} | |
// Stuff mantissa directly into real | |
()@trusted{ *cast(long*)&ldval = msdec; }(); | |
()@trusted{ (cast(ushort*)&ldval)[4] = cast(ushort) e2; }(); | |
import std.math : ldexp; | |
// Exponent is power of 2, not power of 10 | |
ldval = ldexp(ldval,exp); | |
} | |
} | |
else static if (real.mant_dig == 53) | |
{ | |
if (msdec) | |
{ | |
//Exponent bias + 52: | |
//After shifting 52 times left, exp must be 1 | |
int e2 = 0x3FF + 52; | |
// right justify mantissa | |
// first 11 bits must be zero, rest is implied bit + mantissa | |
// shift one time less, do rounding, shift again | |
while ((msdec & 0xFFC0_0000_0000_0000) != 0) | |
{ | |
msdec = ((cast(ulong)msdec) >> 1); | |
e2++; | |
} | |
//Have to shift one more time | |
//and do rounding | |
if ((msdec & 0xFFE0_0000_0000_0000) != 0) | |
{ | |
auto roundUp = (msdec & 0x1); | |
msdec = ((cast(ulong)msdec) >> 1); | |
e2++; | |
if (roundUp) | |
{ | |
msdec += 1; | |
//If mantissa was 0b1111... and we added +1 | |
//the mantissa should be 0b10000 (think of implicit bit) | |
//and the exponent increased | |
if ((msdec & 0x0020_0000_0000_0000) != 0) | |
{ | |
msdec = 0x0010_0000_0000_0000; | |
e2++; | |
} | |
} | |
} | |
// left justify mantissa | |
// bit 11 must be 1 | |
while ((msdec & 0x0010_0000_0000_0000) == 0) | |
{ | |
msdec <<= 1; | |
e2--; | |
} | |
// Stuff mantissa directly into double | |
// (first including implicit bit) | |
()@trusted{ *cast(long *)&ldval = msdec; }(); | |
//Store exponent, now overwriting implicit bit | |
()@trusted{ *cast(long *)&ldval &= 0x000F_FFFF_FFFF_FFFF; }(); | |
()@trusted{ *cast(long *)&ldval |= ((e2 & 0xFFFUL) << 52); }(); | |
import std.math : ldexp; | |
// Exponent is power of 2, not power of 10 | |
ldval = ldexp(ldval,exp); | |
} | |
} | |
else | |
static assert(false, "Floating point format of real type not supported"); | |
goto L6; | |
} | |
else // not hex | |
{ | |
if (toUpper(source.front) == 'N' && !startsWithZero) | |
{ | |
// nan | |
source.popFront(); | |
enforce(!source.empty && toUpper(source.front) == 'A', | |
new ConvException("error converting input to floating point")); | |
source.popFront(); | |
enforce(!source.empty && toUpper(source.front) == 'N', | |
new ConvException("error converting input to floating point")); | |
// skip past the last 'n' | |
source.popFront(); | |
static if (isNarrowString!Source) | |
p = source.assumeUTF; | |
return typeof(return).nan; | |
} | |
bool sawDigits = startsWithZero; | |
while (!source.empty) | |
{ | |
int i = source.front; | |
while (isDigit(i)) | |
{ | |
sawDigits = true; /* must have at least 1 digit */ | |
if (msdec < (0x7FFFFFFFFFFFL-10)/10) | |
msdec = msdec * 10 + (i - '0'); | |
else if (msscale < (0xFFFFFFFF-10)/10) | |
{ | |
lsdec = lsdec * 10 + (i - '0'); | |
msscale *= 10; | |
} | |
else | |
{ | |
exp++; | |
} | |
exp -= dot; | |
source.popFront(); | |
if (source.empty) | |
break; | |
i = source.front; | |
if (i == '_') | |
{ | |
source.popFront(); | |
if (source.empty) | |
break; | |
i = source.front; | |
} | |
} | |
if (i == '.' && !dot) | |
{ | |
source.popFront(); | |
dot++; | |
} | |
else | |
{ | |
break; | |
} | |
} | |
enforce(sawDigits, new ConvException("no digits seen")); | |
} | |
if (!source.empty && (source.front == 'e' || source.front == 'E')) | |
{ | |
char sexp; | |
int e; | |
sexp = 0; | |
source.popFront(); | |
enforce(!source.empty, new ConvException("Unexpected end of input")); | |
switch (source.front) | |
{ | |
case '-': sexp++; | |
goto case; | |
case '+': source.popFront(); | |
break; | |
default: {} | |
} | |
bool sawDigits = 0; | |
e = 0; | |
while (!source.empty && isDigit(source.front)) | |
{ | |
if (e < 0x7FFFFFFF / 10 - 10) // prevent integer overflow | |
{ | |
e = e * 10 + source.front - '0'; | |
} | |
source.popFront(); | |
sawDigits = 1; | |
} | |
exp += (sexp) ? -e : e; | |
enforce(sawDigits, new ConvException("No digits seen.")); | |
} | |
ldval = msdec; | |
if (msscale != 1) /* if stuff was accumulated in lsdec */ | |
ldval = ldval * msscale + lsdec; | |
if (ldval) | |
{ | |
uint u = 0; | |
int pow = 4096; | |
while (exp > 0) | |
{ | |
while (exp >= pow) | |
{ | |
ldval *= postab[u]; | |
exp -= pow; | |
} | |
pow >>= 1; | |
u++; | |
} | |
while (exp < 0) | |
{ | |
while (exp <= -pow) | |
{ | |
ldval *= negtab[u]; | |
enforce(ldval != 0, new ConvException("Range error")); | |
exp += pow; | |
} | |
pow >>= 1; | |
u++; | |
} | |
} | |
L6: // if overflow occurred | |
enforce(ldval != HUGE_VAL, new ConvException("Range error")); | |
L1: | |
static if (isNarrowString!Source) | |
p = source.assumeUTF; | |
return (sign) ? -ldval : ldval; | |
} | |
__gshared char[] test = ['1', '2', '3', '4', '5', '.', '6', '7', '8', '9']; | |
void main() | |
{ | |
enum n = 50_000_000; | |
double result; | |
StopWatch sw; | |
Duration sum; | |
TickDuration last = TickDuration.from!"seconds"(0); | |
foreach(i; 0 .. n) | |
{ | |
sw.start(); | |
// New | |
//result = parse1!double(test); | |
// Old | |
result = parse!double(test); | |
sw.stop(); | |
test = ['1', '2', '3', '4', '5', '.', '6', '7', '8', '9']; | |
auto time = sw.peek() - last; | |
last = sw.peek(); | |
sum += time.to!Duration(); | |
} | |
writeln("Total time: ", sum); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment