From c953c36d11ee6f83b8b33e9ec1d7664ff5e680ed Mon Sep 17 00:00:00 2001 From: Piotr Caban Date: Fri, 22 Nov 2019 16:41:30 +0100 Subject: [PATCH] ucrtbase: Fix hexadecimal floats parsing in strtod. Signed-off-by: Piotr Caban Signed-off-by: Alexandre Julliard --- dlls/msvcrt/string.c | 263 ++++++++++++++++++++++++++++++----- dlls/ucrtbase/tests/string.c | 15 +- 2 files changed, 239 insertions(+), 39 deletions(-) diff --git a/dlls/msvcrt/string.c b/dlls/msvcrt/string.c index 10249592bbe..fa5f7022c17 100644 --- a/dlls/msvcrt/string.c +++ b/dlls/msvcrt/string.c @@ -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<= (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<= '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 || hlpMSVCRT_UI64_MAX/10 || hlp='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 || hlpMSVCRT_UI64_MAX/10 || hlp='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++; diff --git a/dlls/ucrtbase/tests/string.c b/dlls/ucrtbase/tests/string.c index e4146c6fc4c..bfa602c5387 100644 --- a/dlls/ucrtbase/tests/string.c +++ b/dlls/ucrtbase/tests/string.c @@ -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)