From: Piotr Caban Subject: [PATCH] msvcrt: Improve strtod precision. Message-Id: <1218d639-aeba-d48e-bd04-5ccdc0173934@codeweavers.com> Date: Mon, 30 Dec 2019 13:38:26 +0100 This patch fixes strtod precision regression. It also removes floating points operations. Signed-off-by: Piotr Caban --- dlls/msvcrt/msvcrt.h | 1 + dlls/msvcrt/string.c | 157 ++++++++++++++++++++++++----------- dlls/ucrtbase/tests/string.c | 20 +++-- 3 files changed, 123 insertions(+), 55 deletions(-) diff --git a/dlls/msvcrt/msvcrt.h b/dlls/msvcrt/msvcrt.h index c333ddc62f..ee1a159385 100644 --- a/dlls/msvcrt/msvcrt.h +++ b/dlls/msvcrt/msvcrt.h @@ -53,6 +53,7 @@ #define MSVCRT_FLT_MIN_10_EXP (-37) #define MSVCRT_DBL_MAX_10_EXP 308 #define MSVCRT_DBL_MIN_10_EXP (-307) +#define MSVCRT_DBL_DIG 15 #ifdef _WIN64 #define MSVCRT_SIZE_MAX MSVCRT_UI64_MAX #else diff --git a/dlls/msvcrt/string.c b/dlls/msvcrt/string.c index ae1ec4c796..51730f6349 100644 --- a/dlls/msvcrt/string.c +++ b/dlls/msvcrt/string.c @@ -343,8 +343,6 @@ 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, @@ -444,6 +442,8 @@ static double make_double(int sign, int exp, ULONGLONG m, enum round round, int return *((double*)&bits); } +#if _MSVCR_VER >= 140 + static inline int hex2int(char c) { if (c >= '0' && c <= '9') @@ -558,26 +558,105 @@ static double strtod16(int sign, const char *p, char **end, } #endif -static double MSVCRT_mul_pow10(double x, int exp) +struct u128 { + ULONGLONG u[2]; +}; + +static inline struct u128 u128_lshift(struct u128 *u) +{ + struct u128 r; + + r.u[0] = u->u[0] << 1; + r.u[1] = (u->u[1] << 1) + (u->u[0] >> (sizeof(u->u[0])*8-1)); + return r; +} + +static inline struct u128 u128_rshift(struct u128 *u) +{ + struct u128 r; + + r.u[0] = (u->u[0] >> 1) + ((u->u[1] & 1) << (sizeof(u->u[1])*8-1)); + r.u[1] = u->u[1] >> 1; + return r; +} + +static inline void u128_mul10(struct u128 *u) { - BOOL negexp = (exp < 0); - double ret; + struct u128 tmp; - if(negexp) - exp = -exp; - ret = pow(10.0, exp); - return (negexp ? x/ret : x*ret); + tmp = u128_lshift(u); + *u = u128_lshift(&tmp); + *u = u128_lshift(u); + + u->u[0] += tmp.u[0]; + u->u[1] += tmp.u[1]; + if (u->u[0] < tmp.u[0]) u->u[1]++; +} + +static inline void u128_div10(struct u128 *u) +{ + ULONGLONG h, l, r; + + r = u->u[1] % 10; + u->u[1] /= 10; + + h = (r << 32) + (u->u[0] >> 32); + r = h % 10; + h /= 10; + + l = (r << 32) + (u->u[0] & 0xffffffff); + l /= 10; + u->u[0] = (h << 32) + l; +} + +static double convert_e10_to_e2(int sign, int e10, ULONGLONG m, int *err) +{ + int e2 = 0; + struct u128 u128; + + u128.u[0] = m; + u128.u[1] = 0; + + while(e10 > 0) + { + u128_mul10(&u128); + e10--; + + while(u128.u[1] > MSVCRT_UI64_MAX/16) + { + u128 = u128_rshift(&u128); + e2++; + } + } + + while(e10 < 0) + { + while(!(u128.u[1] & (1 << (sizeof(u128.u[1])-1)))) + { + u128 = u128_lshift(&u128); + e2--; + } + + u128_div10(&u128); + e10++; + } + + while(u128.u[1]) + { + u128 = u128_rshift(&u128); + e2++; + } + + return make_double(sign, e2, u128.u[0], ROUND_DOWN, err); } static double strtod_helper(const char *str, char **end, MSVCRT__locale_t locale, int *err) { - int exp1=0, exp2=0, exp3=0, sign=1; MSVCRT_pthreadlocinfo locinfo; unsigned __int64 d=0, hlp; - BOOL found_digit = FALSE; - unsigned fpcontrol; + int exp=0, sign=1; const char *p; - double ret; + BOOL found_digit = FALSE; if(err) *err = 0; @@ -631,13 +710,13 @@ static double strtod_helper(const char *str, char **end, MSVCRT__locale_t locale found_digit = TRUE; hlp = d * 10 + *p++ - '0'; if(d>MSVCRT_UI64_MAX/10 || hlp='0' && *p<='9') { - exp1++; + exp++; p++; } @@ -650,7 +729,7 @@ static double strtod_helper(const char *str, char **end, MSVCRT__locale_t locale if(d>MSVCRT_UI64_MAX/10 || hlp='0' && *p<='9') p++; @@ -679,9 +758,9 @@ static double strtod_helper(const char *str, char **end, MSVCRT__locale_t locale } e *= s; - if(exp1<0 && e<0 && exp1+e>=0) exp1 = INT_MIN; - else if(exp1>0 && e>0 && exp1+e<0) exp1 = INT_MAX; - else exp3 = e; + 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--; @@ -689,39 +768,19 @@ static double strtod_helper(const char *str, char **end, MSVCRT__locale_t locale } } - fpcontrol = _control87(0, 0); - _control87(MSVCRT__EM_DENORMAL|MSVCRT__EM_INVALID|MSVCRT__EM_ZERODIVIDE - |MSVCRT__EM_OVERFLOW|MSVCRT__EM_UNDERFLOW|MSVCRT__EM_INEXACT|MSVCRT__PC_64, - MSVCRT__MCW_EM | MSVCRT__MCW_PC ); - - /* take the number without exponent and convert it into a double */ - ret = MSVCRT_mul_pow10(d, exp1); - /* shift the number to the representation where the first non-zero digit is in the ones place */ - exp2 = (ret != 0.0 ? (int)round(log10(ret)) : 0); - if (exp3-exp2 >= MSVCRT_FLT_MIN_10_EXP && exp3-exp2 <= MSVCRT_FLT_MAX_10_EXP) - exp2 = 0; /* only bother to take this extra step with very small or very large numbers */ - /* incorporate an additional shift to deal with floating point denormal values (if necessary) */ - if(exp3-exp2 < MSVCRT_DBL_MIN_10_EXP) - exp2 += exp3-exp2-MSVCRT_DBL_MIN_10_EXP; - ret = MSVCRT_mul_pow10(ret, exp2); - /* apply the exponent (and undo any shift) */ - ret = MSVCRT_mul_pow10(ret, exp3-exp2); - /* apply the sign bit */ - ret *= sign; - - _control87( fpcontrol, MSVCRT__MCW_EM | MSVCRT__MCW_PC ); - - if((d && ret==0.0) || isinf(ret)) { - if(err) - *err = MSVCRT_ERANGE; - else - *MSVCRT__errno() = MSVCRT_ERANGE; - } - if(end) *end = (char*)p; - return ret; + if(!err) err = MSVCRT__errno(); + if(!d) return make_double(sign, exp, d, ROUND_ZERO, err); + if(exp > MSVCRT_DBL_MAX_10_EXP) + return make_double(sign, INT_MAX, d, ROUND_ZERO, err); + /* Count part of exponent stored in denormalized mantissa. */ + /* Increase exponent range to handle subnormals. */ + if(exp < MSVCRT_DBL_MIN_10_EXP-MSVCRT_DBL_DIG-18) + return make_double(sign, INT_MIN, d, ROUND_ZERO, err); + + return convert_e10_to_e2(sign, exp, d, err); } /********************************************************************* diff --git a/dlls/ucrtbase/tests/string.c b/dlls/ucrtbase/tests/string.c index bfa602c538..0bb3feaa2f 100644 --- a/dlls/ucrtbase/tests/string.c +++ b/dlls/ucrtbase/tests/string.c @@ -118,16 +118,19 @@ static BOOL local_isnan(double d) return d != d; } -#define test_strtod_str(string, value, length) _test_strtod_str(__LINE__, string, value, length) -static void _test_strtod_str(int line, const char* string, double value, int length) +#define test_strtod_str(string, value, length) _test_strtod_str(__LINE__, string, value, length, FALSE) +#define test_strtod_str_todo(string, value, length) _test_strtod_str(__LINE__, string, value, length, TRUE) +static void _test_strtod_str(int line, const char* string, double value, int length, BOOL todo) { char *end; double d; d = p_strtod(string, &end); - if (local_isnan(value)) - ok_(__FILE__, line)(local_isnan(d), "d = %.16le (\"%s\")\n", d, string); - else - ok_(__FILE__, line)(d == value, "d = %.16le (\"%s\")\n", d, string); + todo_wine_if(todo) { + if (local_isnan(value)) + ok_(__FILE__, line)(local_isnan(d), "d = %.16le (\"%s\")\n", d, string); + else + 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); } @@ -173,6 +176,11 @@ static void test_strtod(void) test_strtod_str("0x1ffffffffffffe.80000000000000000001", 9007199254740991.0, 37); test_strtod_str("0x1fffffffffffff.80000000000000000000", 9007199254740992.0, 37); test_strtod_str("0x1fffffffffffff.80000000000000000001", 9007199254740992.0, 37); + + test_strtod_str("4.0621786324484881721115322e-53", 4.0621786324484881721115322e-53, 31); + test_strtod_str_todo("1.8905590910042396899370942", 1.8905590910042396899370942, 27); + test_strtod_str("2.2250738585072014e-308", 2.2250738585072014e-308, 23); + test_strtod_str("4.9406564584124654e-324", 4.9406564584124654e-324, 23); } static void test__memicmp(void)