Skip to content

Commit

Permalink
Add a hex parsing strtod for MSVC <= 2013.
Browse files Browse the repository at this point in the history
WIP: proper rounding/truncation not implemented yet!
  • Loading branch information
JohanEngelen committed Mar 3, 2015
1 parent 192583d commit 417cded
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 0 deletions.
14 changes: 14 additions & 0 deletions dmd2/root/port.c
Original file line number Diff line number Diff line change
Expand Up @@ -282,10 +282,24 @@ double Port::strtod(const char *p, char **endp)
// from backend/strtold.c, renamed to avoid clash with decl in stdlib.h
longdouble strtold_dm(const char *p,char **endp);

#if _MSC_VER <= 1800
// from strtod_hex.c, parses *only* hexadecimal 64bit-float literals
longdouble strtod_hex(const char* p, char** endp);
#endif

longdouble Port::strtold(const char *p, char **endp)
{
#if IN_LLVM
# if _MSC_VER > 1800
return ::strtold(p, endp);
# else
// older MSVC versions do not support hexadecimal floating point values, so we have to implement our own conversion
longdouble val = ::strtold(p, endp);
if (!val) { // if val is zero, try parsing as hex float
val = strtod_hex(p, endp);
}
return val;
# endif
#else
return ::strtold_dm(p, endp);
#endif
Expand Down
146 changes: 146 additions & 0 deletions dmd2/root/strtod_hex.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
//===-- strtod_hex.c ------------------------------------------------------===//
//
// LDC – the LLVM D compiler
//
// This file is distributed under the BSD-style LDC license. See the LICENSE
// file for details.
//
//
//
// MSVC's strtod does not support hexadecimal floating points before MSVC 2014.
// strtod_hex is a hexadecimal float literal parser to be used within port.c
//
//===----------------------------------------------------------------------===//

#if _MSC_VER <= 1800

#include <cctype>
#include "longdouble.h"

longdouble strtod_hex(const char* p, char** endp) {
// References: http://dlang.org/lex.html
// http://www.exploringbinary.com/hexadecimal-floating-point-constants
// pages 57-58 of the C99 specification (http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf)
// http://www.cplusplus.com/reference/cstdlib/strtod

// perhaps this function can rely on correctly formed hex floats (dmd2 front-end lexer), but I am not sure

while (isspace(*p)) p++;

if ( (p[0] != '0') || ((p[1] != 'x') && (p[1] != 'X')) )
return 0;

p += 2;

bool sign = false; // positive
unsigned num_digits = 0;
bool dot = false;
int exponent = -4;
unsigned __int64 fraction = 0;
while (true) {
char c = *p;
if (c == '_') {
++p;
}
else if (isxdigit(c)) {
unsigned val = isalpha(c) ? ((c|0x20) - ('a'-10)) : (c - '0');
if (num_digits < 14) {
fraction = (fraction << 4) + val;
if (fraction) { // ignore leading zeros
++num_digits;
if (!dot) {
exponent += 4;
}
}
} else {
if (!dot) {
exponent += 4;
}
}
++p;
}
else if ( (c == '.') && !dot ) {
dot = true;
++p;
}
else {
break;
}
}

if (!num_digits) {
// the input is "0x" without valid digits after
return 0;
}

// check for exponent (decimal number)
int explicit_exponent = 0;
if ((*p == 'p') || (*p == 'P')) {
++p;

bool expsign = false;
if (p[0]=='+')
++p;
else if (*p =='-') {
expsign = true;
++p;
}

while (true) {
char c = *p;
if (c == '_') {
++p;
}
else if (isdigit(c)) { // the exponent is decimal, no hex allowed!
unsigned val = c - '0';
explicit_exponent = explicit_exponent*10 + val;
++p;
}
else {
break;
}
}

if (expsign) explicit_exponent *= -1;
}
else {
// handle error here? I believe that: dlang spec mandates explicit exponent, but C99 doesn't
}

if ((*p == 'f') || (*p == 'F') || (*p == 'l') || (*p == 'L')) {
// handle trailing L or F (F = rounding needed at bitlocation where 32-bit float would truncate?)
++p;
}

if (endp) {
*endp = (char*) p; // ugly cast needed to remove 'const'
}


fraction = fraction << ((14 - num_digits)*4);

unsigned __int64 excess = fraction >> 53;
while (excess) {
++exponent;
fraction = fraction >> 1;
excess = excess >> 1;
}

// exponent has 1023 offset (zero == 1023)
exponent = explicit_exponent + exponent + 1023;

/*
IEEE-754 64-bit floating point (from MSB to LSB) =
1 sign bit
11 bit exponent
52 bit fraction (52/4 = 13 hex digits max + leading 1)
*/


unsigned __int64 u = (sign ? 0x8000000000000000 : 0)
| ( *(unsigned __int64*)(&exponent) & (0x7FF)) << 52
| fraction & (0x000FFFFFFFFFFFFF);

return *(double*)&u;
}
#endif

0 comments on commit 417cded

Please sign in to comment.