Erich E. Hoover : msvcrt: Implement strtod without using 'long double'.

Alexandre Julliard julliard at winehq.org
Thu Dec 19 16:00:05 CST 2019


Module: wine
Branch: master
Commit: c22af971c287933a137c9fbecc81823812e12b7a
URL:    https://source.winehq.org/git/wine.git/?a=commit;h=c22af971c287933a137c9fbecc81823812e12b7a

Author: Erich E. Hoover <erich.e.hoover at gmail.com>
Date:   Wed Dec 18 15:48:11 2019 -0700

msvcrt: Implement strtod without using 'long double'.

Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=48160
Signed-off-by: Erich E. Hoover <erich.e.hoover at gmail.com>
Signed-off-by: Alexandre Julliard <julliard at winehq.org>

---

 dlls/msvcrt/msvcrt.h       |  2 ++
 dlls/msvcrt/string.c       | 67 ++++++++++++++++++++++++++++++----------------
 dlls/msvcrt/tests/string.c | 46 ++++++++++++++++++++++++++++---
 3 files changed, 89 insertions(+), 26 deletions(-)

diff --git a/dlls/msvcrt/msvcrt.h b/dlls/msvcrt/msvcrt.h
index 3933c0fa3e..2ac7977182 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 b78d93a917..03d80a5dec 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<d) {
-            exp++;
+            exp1++;
             break;
         } else
             d = hlp;
     }
     while(*p>='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<d)
             break;
         d = hlp;
-        exp--;
+        exp1--;
     }
     while(*p>='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);
-
-    negexp = (exp < 0);
-    if(negexp)
-        exp = -exp;
-    while(exp) {
-        if(exp & 1)
-            lret *= expcnt;
-        exp /= 2;
-        expcnt = expcnt*expcnt;
-    }
-    ret = (long double)sign * (negexp ? d/lret : d*lret);
-
-    _control87(fpcontrol, 0xffffffff);
+               |MSVCRT__EM_OVERFLOW|MSVCRT__EM_UNDERFLOW|MSVCRT__EM_INEXACT|MSVCRT__PC_64,
+               MSVCRT__MCW_EM | MSVCRT__MCW_PC );
+
+    /* 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;
+    }
+    /* 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, 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 f814a22bdb..beca47f1b6 100644
--- a/dlls/msvcrt/tests/string.c
+++ b/dlls/msvcrt/tests/string.c
@@ -28,6 +28,7 @@
 #include <locale.h>
 #include <errno.h>
 #include <limits.h>
+#include <float.h>
 #include <math.h>
 
 /* 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);




More information about the wine-cvs mailing list