From c22af971c287933a137c9fbecc81823812e12b7a Mon Sep 17 00:00:00 2001 From: "Erich E. Hoover" Date: Wed, 18 Dec 2019 15:48:11 -0700 Subject: [PATCH] msvcrt: Implement strtod without using 'long double'. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=48160 Signed-off-by: Erich E. Hoover Signed-off-by: Alexandre Julliard --- dlls/msvcrt/msvcrt.h | 2 ++ dlls/msvcrt/string.c | 61 +++++++++++++++++++++++++------------- dlls/msvcrt/tests/string.c | 46 ++++++++++++++++++++++++++-- 3 files changed, 86 insertions(+), 23 deletions(-) diff --git a/dlls/msvcrt/msvcrt.h b/dlls/msvcrt/msvcrt.h index 3933c0fa3e1..2ac7977182f 100644 --- a/dlls/msvcrt/msvcrt.h +++ b/dlls/msvcrt/msvcrt.h @@ -49,6 +49,8 @@ #define MSVCRT_I64_MIN (-MSVCRT_I64_MAX-1) #define MSVCRT_UI64_MAX (((unsigned __int64)0xffffffff << 32) | 0xffffffff) #define MSVCRT_MB_LEN_MAX 5 +#define MSVCRT_DBL_MAX_10_EXP 308 +#define MSVCRT_DBL_MIN_10_EXP (-307) #ifdef _WIN64 #define MSVCRT_SIZE_MAX MSVCRT_UI64_MAX #else diff --git a/dlls/msvcrt/string.c b/dlls/msvcrt/string.c index b78d93a917a..03d80a5dec6 100644 --- a/dlls/msvcrt/string.c +++ b/dlls/msvcrt/string.c @@ -558,16 +558,26 @@ static double strtod16(int sign, const char *p, char **end, } #endif +static double MSVCRT_mul_pow10(double x, int exp) +{ + BOOL negexp = (exp < 0); + double ret; + + if(negexp) + exp = -exp; + ret = pow(10.0, exp); + return (negexp ? x/ret : x*ret); +} + static double strtod_helper(const char *str, char **end, MSVCRT__locale_t locale, int *err) { + BOOL found_digit = FALSE, overflow, underflow; + int exp1=0, exp2=0, exp3=0, sign=1; MSVCRT_pthreadlocinfo locinfo; unsigned __int64 d=0, hlp; unsigned fpcontrol; - int exp=0, sign=1; const char *p; double ret; - long double lret=1, expcnt = 10; - BOOL found_digit = FALSE, negexp; if(err) *err = 0; @@ -621,13 +631,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') { - exp++; + exp1++; p++; } @@ -640,7 +650,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++; @@ -669,9 +679,9 @@ static double strtod_helper(const char *str, char **end, MSVCRT__locale_t locale } 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; + 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; } else { if(*p=='-' || *p=='+') p--; @@ -681,20 +691,31 @@ 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, 0xffffffff); + |MSVCRT__EM_OVERFLOW|MSVCRT__EM_UNDERFLOW|MSVCRT__EM_INEXACT|MSVCRT__PC_64, + MSVCRT__MCW_EM | MSVCRT__MCW_PC ); - negexp = (exp < 0); - if(negexp) - exp = -exp; - while(exp) { - if(exp & 1) - lret *= expcnt; - exp /= 2; - expcnt = expcnt*expcnt; + /* if we have a simple case then just calculate the result directly */ + overflow = (exp3-exp1 > MSVCRT_DBL_MAX_10_EXP); + underflow = (exp3-exp1 < MSVCRT_DBL_MIN_10_EXP); + if(!overflow && !underflow) { + exp1 += exp3; + exp3 = 0; } - ret = (long double)sign * (negexp ? d/lret : d*lret); + /* 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 */ + if(overflow || underflow) + exp2 = (ret != 0.0 ? (int)log10(ret) : 0); + /* 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, 0xffffffff); + _control87( fpcontrol, MSVCRT__MCW_EM | MSVCRT__MCW_PC ); if((d && ret==0.0) || isinf(ret)) { if(err) diff --git a/dlls/msvcrt/tests/string.c b/dlls/msvcrt/tests/string.c index f814a22bdb6..beca47f1b61 100644 --- a/dlls/msvcrt/tests/string.c +++ b/dlls/msvcrt/tests/string.c @@ -28,6 +28,7 @@ #include #include #include +#include #include /* make it use a definition from string.h */ @@ -1885,6 +1886,13 @@ static inline BOOL almost_equal(double d1, double d2) { return FALSE; } +static inline BOOL large_almost_equal(double d1, double d2) { + double diff = fabs(d1-d2); + if(diff / (fabs(d1) + fabs(d2)) < DBL_EPSILON) + return TRUE; + return FALSE; +} + static void test__strtod(void) { const char double1[] = "12.1"; @@ -1990,6 +1998,9 @@ static void test__strtod(void) errno = 0xdeadbeef; strtod("-1d309", NULL); ok(errno == ERANGE, "errno = %x\n", errno); + + d = strtod("1.7976931348623158e+308", NULL); + ok(almost_equal(d, DBL_MAX), "d = %lf (%lf)\n", d, DBL_MAX); } static void test_mbstowcs(void) @@ -2984,11 +2995,28 @@ static void test_tolower(void) setlocale(LC_ALL, "C"); } +static double mul_pow10(double x, double exp) +{ + int fpexcept = _EM_DENORMAL|_EM_INVALID|_EM_ZERODIVIDE|_EM_OVERFLOW|_EM_UNDERFLOW|_EM_INEXACT; + BOOL negexp = (exp < 0); + int fpcontrol; + double ret; + + if(negexp) + exp = -exp; + fpcontrol = _control87(0, 0); + _control87(fpexcept, 0xffffffff); + ret = pow(10.0, exp); + ret = (negexp ? x/ret : x*ret); + _control87(fpcontrol, 0xffffffff); + return ret; +} + static void test__atodbl(void) { _CRT_DOUBLE d; char num[32]; - int ret; + int i, j, ret; if(!p__atodbl_l) { /* Old versions of msvcrt use different values for _OVERFLOW and _UNDERFLOW @@ -3029,13 +3057,25 @@ static void test__atodbl(void) ok(ret == 0, "_atodbl(&d, \"123\") returned %d, expected 0\n", ret); ok(d.x == 123, "d.x = %lf, expected 123\n", d.x); + /* check over the whole range of (simple) normal doubles */ + for (j = DBL_MIN_10_EXP; j <= DBL_MAX_10_EXP; j++) { + for (i = 1; i <= 9; i++) { + double expected = mul_pow10(i, j); + if (expected < DBL_MIN || expected > DBL_MAX) continue; + snprintf(num, sizeof(num), "%de%d", i, j); + ret = _atodbl(&d, num); + ok(large_almost_equal(d.x, expected), "d.x = %le, expected %le\n", d.x, expected); + } + } + + /* check with denormal doubles */ strcpy(num, "1e-309"); ret = p__atodbl_l(&d, num, NULL); ok(ret == _UNDERFLOW, "_atodbl_l(&d, \"1e-309\", NULL) returned %d, expected _UNDERFLOW\n", ret); - ok(d.x!=0 && almost_equal(d.x, 0), "d.x = %le, expected 0\n", d.x); + ok(d.x!=0 && almost_equal(d.x, 0.1e-308), "d.x = %le, expected 0.1e-308\n", d.x); ret = _atodbl(&d, num); ok(ret == _UNDERFLOW, "_atodbl(&d, \"1e-309\") returned %d, expected _UNDERFLOW\n", ret); - ok(d.x!=0 && almost_equal(d.x, 0), "d.x = %le, expected 0\n", d.x); + ok(d.x!=0 && almost_equal(d.x, 0.1e-308), "d.x = %le, expected 0.1e-308\n", d.x); strcpy(num, "1e309"); ret = p__atodbl_l(&d, num, NULL);