Piotr Caban : msvcrt: Improve strtod precision.

Alexandre Julliard julliard at winehq.org
Mon Dec 30 15:59:43 CST 2019


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

Author: Piotr Caban <piotr at codeweavers.com>
Date:   Mon Dec 30 13:23:47 2019 +0100

msvcrt: Improve strtod precision.

This fixes strtod precision regression. It also removes floating
point operations.

Signed-off-by: Piotr Caban <piotr at codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard at winehq.org>

---

 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<d) {
-            exp1++;
+            exp++;
             break;
         } else
             d = hlp;
     }
     while(*p>='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<d)
             break;
         d = hlp;
-        exp1--;
+        exp--;
     }
     while(*p>='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)




More information about the wine-cvs mailing list