ucrtbase: Fix hexadecimal floats parsing in strtod.

Signed-off-by: Piotr Caban <piotr@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Piotr Caban 2019-11-22 16:41:30 +01:00 committed by Alexandre Julliard
parent e2423823fc
commit c953c36d11
2 changed files with 239 additions and 39 deletions

View file

@ -325,6 +325,221 @@ void CDECL MSVCRT__swab(char* src, char* dst, int len)
}
}
#if _MSVCR_VER >= 140
enum round {
ROUND_ZERO, /* only used when dropped part contains only zeros */
ROUND_DOWN,
ROUND_EVEN,
ROUND_UP
};
static double make_double(int sign, int exp, ULONGLONG m, enum round round, int *err)
{
#define EXP_BITS 11
#define MANT_BITS 53
ULONGLONG bits = 0;
TRACE("%c %s *2^%d (round %d)\n", sign == -1 ? '-' : '+', wine_dbgstr_longlong(m), exp, round);
if (!m) return sign * 0.0;
/* make sure that we don't overflow modifying exponent */
if (exp > 1<<EXP_BITS)
{
if (err) *err = MSVCRT_ERANGE;
return sign * INFINITY;
}
if (exp < -(1<<EXP_BITS))
{
if (err) *err = MSVCRT_ERANGE;
return sign * 0.0;
}
exp += MANT_BITS - 1;
/* normalize mantissa */
while(m < (ULONGLONG)1 << (MANT_BITS-1))
{
m <<= 1;
exp--;
}
while(m >= (ULONGLONG)1 << MANT_BITS)
{
if (m & 1 || round != ROUND_ZERO)
{
if (!(m & 1)) round = ROUND_DOWN;
else if(round == ROUND_ZERO) round = ROUND_EVEN;
else round = ROUND_UP;
}
m >>= 1;
exp++;
}
/* handle subnormal that falls into regular range due to rounding */
exp += (1 << (EXP_BITS-1)) - 1;
if (!exp && (round == ROUND_UP || (round == ROUND_EVEN && m & 1)))
{
if (m + 1 >= (ULONGLONG)1 << MANT_BITS)
{
m++;
m >>= 1;
exp++;
round = ROUND_DOWN;
}
}
/* handle subnormals */
if (exp <= 0)
m >>= 1;
while(m && exp<0)
{
m >>= 1;
exp++;
}
/* round mantissa */
if (round == ROUND_UP || (round == ROUND_EVEN && m & 1))
{
m++;
if (m >= (ULONGLONG)1 << MANT_BITS)
{
exp++;
m >>= 1;
}
}
if (exp >= 1<<EXP_BITS)
{
if (err) *err = MSVCRT_ERANGE;
return sign * INFINITY;
}
if (!m || exp < 0)
{
if (err) *err = MSVCRT_ERANGE;
return sign * 0.0;
}
if (sign == -1) bits |= (ULONGLONG)1 << (MANT_BITS + EXP_BITS - 1);
bits |= (ULONGLONG)exp << (MANT_BITS - 1);
bits |= m & (((ULONGLONG)1 << (MANT_BITS - 1)) - 1);
TRACE("returning %s\n", wine_dbgstr_longlong(bits));
return *((double*)&bits);
}
static inline int hex2int(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
else if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
else if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static double strtod16(int sign, const char *p, char **end,
MSVCRT_pthreadlocinfo locinfo, int *err)
{
enum round round = ROUND_ZERO;
BOOL found_digit = FALSE;
ULONGLONG m = 0;
int val, exp = 0;
while(m < MSVCRT_UI64_MAX/16)
{
val = hex2int(*p);
if (val == -1) break;
found_digit = TRUE;
p++;
m = m*16 + val;
}
while(1)
{
val = hex2int(*p);
if (val == -1) break;
p++;
exp += 4;
if (val || round != ROUND_ZERO)
{
if (val < 8) round = ROUND_DOWN;
else if (val == 8 && round == ROUND_ZERO) round = ROUND_EVEN;
else round = ROUND_UP;
}
}
if(*p == *locinfo->lconv->decimal_point)
p++;
else if (!found_digit)
{
if(end) *end = (char*)p - 1;
return 0.0;
}
while(m <= MSVCRT_UI64_MAX/16)
{
val = hex2int(*p);
if (val == -1) break;
found_digit = TRUE;
p++;
m = m*16 + val;
exp -= 4;
}
while(1)
{
val = hex2int(*p);
if (val == -1) break;
p++;
if (val || round != ROUND_ZERO)
{
if (val < 8) round = ROUND_DOWN;
else if (val == 8 && round == ROUND_ZERO) round = ROUND_EVEN;
else round = ROUND_UP;
}
}
if (!found_digit)
{
if(end) *end = (char*)p - 2;
return 0.0;
}
if(*p=='p' || *p=='P') {
int e=0, s=1;
p++;
if(*p == '-') {
s = -1;
p++;
} else if(*p == '+')
p++;
if(*p>='0' && *p<='9') {
while(*p>='0' && *p<='9') {
if(e>INT_MAX/10 || (e=e*10+*p-'0')<0)
e = INT_MAX;
p++;
}
e *= s;
if(exp<0 && e<0 && exp+e>=0) exp = INT_MIN;
else if(exp>0 && e>0 && exp+e<0) exp = INT_MAX;
else exp += e;
} else {
if(*p=='-' || *p=='+')
p--;
p--;
}
}
if (end) *end = (char*)p;
return make_double(sign, exp, m, round, err);
}
#endif
static double strtod_helper(const char *str, char **end, MSVCRT__locale_t locale, int *err)
{
MSVCRT_pthreadlocinfo locinfo;
@ -335,7 +550,6 @@ static double strtod_helper(const char *str, char **end, MSVCRT__locale_t locale
double ret;
long double lret=1, expcnt = 10;
BOOL found_digit = FALSE, negexp;
int base = 10;
if(err)
*err = 0;
@ -379,33 +593,22 @@ static double strtod_helper(const char *str, char **end, MSVCRT__locale_t locale
return NAN;
}
if(p[0] == '0' && MSVCRT__tolower_l(p[1], locale) == 'x') {
base = 16;
expcnt = 2;
if(p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
p += 2;
return strtod16(sign, p, end, locinfo, err);
}
#endif
while((*p>='0' && *p<='9') ||
(base == 16 && ((*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F')))) {
char c = *p++;
int val;
while(*p>='0' && *p<='9') {
found_digit = TRUE;
if (c>='0' && c<='9')
val = c - '0';
else if (c >= 'a' && c <= 'f')
val = 10 + c - 'a';
else
val = 10 + c - 'A';
hlp = d*base+val;
if(d>MSVCRT_UI64_MAX/base || hlp<d) {
hlp = d * 10 + *p++ - '0';
if(d>MSVCRT_UI64_MAX/10 || hlp<d) {
exp++;
break;
} else
d = hlp;
}
while((*p>='0' && *p<='9') ||
(base == 16 && ((*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F')))) {
while(*p>='0' && *p<='9') {
exp++;
p++;
}
@ -413,25 +616,15 @@ static double strtod_helper(const char *str, char **end, MSVCRT__locale_t locale
if(*p == *locinfo->lconv->decimal_point)
p++;
while((*p>='0' && *p<='9') ||
(base == 16 && ((*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F')))) {
char c = *p++;
int val;
while(*p>='0' && *p<='9') {
found_digit = TRUE;
if (c>='0' && c<='9')
val = c - '0';
else if (c >= 'a' && c <= 'f')
val = 10 + c - 'a';
else
val = 10 + c - 'A';
hlp = d*base+val;
if(d>MSVCRT_UI64_MAX/base || hlp<d)
hlp = d * 10 + *p++ - '0';
if(d>MSVCRT_UI64_MAX/10 || hlp<d)
break;
d = hlp;
exp--;
}
while((*p>='0' && *p<='9') ||
(base == 16 && ((*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F'))))
while(*p>='0' && *p<='9')
p++;
if(!found_digit) {
@ -440,11 +633,7 @@ static double strtod_helper(const char *str, char **end, MSVCRT__locale_t locale
return 0.0;
}
if(base == 16)
exp *= 4;
if((base == 10 && (*p=='e' || *p=='E' || *p=='d' || *p=='D')) ||
(base == 16 && (*p=='p' || *p=='P'))) {
if(*p=='e' || *p=='E' || *p=='d' || *p=='D') {
int e=0, s=1;
p++;

View file

@ -125,9 +125,9 @@ static void _test_strtod_str(int line, const char* string, double value, int len
double d;
d = p_strtod(string, &end);
if (local_isnan(value))
ok_(__FILE__, line)(local_isnan(d), "d = %lf (\"%s\")\n", d, string);
ok_(__FILE__, line)(local_isnan(d), "d = %.16le (\"%s\")\n", d, string);
else
ok_(__FILE__, line)(d == value, "d = %lf (\"%s\")\n", d, string);
ok_(__FILE__, line)(d == value, "d = %.16le (\"%s\")\n", d, string);
ok_(__FILE__, line)(end == string + length, "incorrect end (%d, \"%s\")\n", (int)(end - string), string);
}
@ -162,6 +162,17 @@ static void test_strtod(void)
test_strtod_str("0x1.1p1", 2.125, 7);
test_strtod_str("0x1.A", 1.625, 5);
test_strtod_str("0x1p1a", 2, 5);
test_strtod_str("0xp3", 0, 1);
test_strtod_str("0x.", 0, 1);
test_strtod_str("0x.8", 0.5, 4);
test_strtod_str("0x.8p", 0.5, 4);
test_strtod_str("0x0p10000000000000000000000000", 0, 30);
test_strtod_str("0x1p-1026", 1.3906711615670009e-309, 9);
test_strtod_str("0x1ffffffffffffe.80000000000000000000", 9007199254740990.0, 37);
test_strtod_str("0x1ffffffffffffe.80000000000000000001", 9007199254740991.0, 37);
test_strtod_str("0x1fffffffffffff.80000000000000000000", 9007199254740992.0, 37);
test_strtod_str("0x1fffffffffffff.80000000000000000001", 9007199254740992.0, 37);
}
static void test__memicmp(void)