[PATCH] msvcrt: Make the cosh/sinh/tanh functions NAN preserving

Martin Storsjo martin at martin.st
Tue Jul 27 17:16:10 CDT 2021


When a NAN is fed as input, return it as-is without mangling
it through the calculation (which e.g. loses the sign of the
input value).

This fixes a regression in a testcase of mine, after switching
to the internal implementation of these functions.

Signed-off-by: Martin Storsjo <martin at martin.st>
---
v4: Integrated the handling in cosh/sinh/coshf/sinhf into the
existing algorithm, as requested. Changed the tanh/tanhf
implementation to readd the sign bit instead of storing the
original value, and set the qnan bit too.

Changed the test to not require an exact match for the mantissa
bits for i386, as msvcrt.dll doesn't provide that.
---
 dlls/msvcrt/math.c       | 42 +++++++++++++++++++++++++++++++++-------
 dlls/msvcrt/tests/misc.c | 34 ++++++++++++++++++++++++++++++++
 2 files changed, 69 insertions(+), 7 deletions(-)

diff --git a/dlls/msvcrt/math.c b/dlls/msvcrt/math.c
index d21883757a2..4671f4f4871 100644
--- a/dlls/msvcrt/math.c
+++ b/dlls/msvcrt/math.c
@@ -1137,9 +1137,11 @@ static float __expo2f(float x, float sign)
 float CDECL coshf( float x )
 {
     UINT32 ui = *(UINT32*)&x;
+    UINT32 sign;
     float t;
 
     /* |x| */
+    sign = ui & 0x80000000;
     ui &= 0x7fffffff;
     x = *(float*)&ui;
 
@@ -1160,7 +1162,10 @@ float CDECL coshf( float x )
     }
 
     /* |x| > log(FLT_MAX) or nan */
-    t = __expo2f(x, 1.0f);
+    if (ui > 0x7f800000)
+        *(DWORD*)&t = *(DWORD*)&x | sign | 0x400000;
+    else
+        t = __expo2f(x, 1.0f);
     return t;
 }
 
@@ -1674,12 +1679,14 @@ float CDECL sinf( float x )
 float CDECL sinhf( float x )
 {
     UINT32 ui = *(UINT32*)&x;
+    UINT32 sign;
     float t, h, absx;
 
     h = 0.5;
     if (ui >> 31)
         h = -h;
     /* |x| */
+    sign = ui & 0x80000000;
     ui &= 0x7fffffff;
     absx = *(float*)&ui;
 
@@ -1695,7 +1702,10 @@ float CDECL sinhf( float x )
     }
 
     /* |x| > logf(FLT_MAX) or nan */
-    t = __expo2f(absx, 2 * h);
+    if (ui > 0x7f800000)
+        *(DWORD*)&t = *(DWORD*)&x | sign | 0x400000;
+    else
+        t = __expo2f(absx, 2 * h);
     return t;
 }
 
@@ -1885,10 +1895,14 @@ float CDECL tanhf( float x )
     if (ui > 0x3f0c9f54) {
         /* |x| > log(3)/2 ~= 0.5493 or nan */
         if (ui > 0x41200000) {
+            if (isnan(x)) {
+                *(UINT32*)&x = ui | ((UINT32)sign << 31) | 0x400000;
 #if _MSVCR_VER < 140
-            if (isnan(x))
                 return math_error(_DOMAIN, "tanhf", x, 0, x);
+#else
+                return x;
 #endif
+            }
             /* |x| > 10 */
             fp_barrierf(x + 0x1p120f);
             t = 1 + 0 / x;
@@ -2766,10 +2780,12 @@ static double __expo2(double x, double sign)
 double CDECL cosh( double x )
 {
     UINT64 ux = *(UINT64*)&x;
+    UINT64 sign;
     UINT32 w;
     double t;
 
     /* |x| */
+    sign = ux & 0x8000000000000000ULL;
     ux &= (uint64_t)-1 / 2;
     x = *(double*)&ux;
     w = ux >> 32;
@@ -2793,7 +2809,10 @@ double CDECL cosh( double x )
 
     /* |x| > log(DBL_MAX) or nan */
     /* note: the result is stored to handle overflow */
-    t = __expo2(x, 1.0);
+    if (w > 0x7ff00000)
+        *(UINT64*)&t = *(UINT64*)&x | sign | 0x0008000000000000ULL;
+    else
+        t = __expo2(x, 1.0);
     return t;
 }
 
@@ -4032,6 +4051,7 @@ double CDECL sin( double x )
 double CDECL sinh( double x )
 {
     UINT64 ux = *(UINT64*)&x;
+    UINT64 sign;
     UINT32 w;
     double t, h, absx;
 
@@ -4039,6 +4059,7 @@ double CDECL sinh( double x )
     if (ux >> 63)
         h = -h;
     /* |x| */
+    sign = ux & 0x8000000000000000ULL;
     ux &= (UINT64)-1 / 2;
     absx = *(double*)&ux;
     w = ux >> 32;
@@ -4056,7 +4077,10 @@ double CDECL sinh( double x )
 
     /* |x| > log(DBL_MAX) or nan */
     /* note: the result is stored to handle overflow */
-    t = __expo2(absx, 2 * h);
+    if (w > 0x7ff00000)
+        *(UINT64*)&t = *(UINT64*)&x | sign | 0x0008000000000000ULL;
+    else
+        t = __expo2(absx, 2 * h);
     return t;
 }
 
@@ -4331,11 +4355,15 @@ double CDECL tanh( double x )
     if (w > 0x3fe193ea) {
         /* |x| > log(3)/2 ~= 0.5493 or nan */
         if (w > 0x40340000) {
+            if (isnan(x)) {
+                *(UINT64*)&x = ui | ((UINT64)sign << 63) | 0x0008000000000000ULL;
 #if _MSVCR_VER < 140
-            if (isnan(x))
                 return math_error(_DOMAIN, "tanh", x, 0, x);
+#else
+                return x;
 #endif
-            /* |x| > 20 or nan */
+            }
+            /* |x| > 20 */
             /* note: this branch avoids raising overflow */
             fp_barrier(x + 0x1p120f);
             t = 1 - 0 / x;
diff --git a/dlls/msvcrt/tests/misc.c b/dlls/msvcrt/tests/misc.c
index b38915596df..bab6211450a 100644
--- a/dlls/msvcrt/tests/misc.c
+++ b/dlls/msvcrt/tests/misc.c
@@ -491,9 +491,36 @@ static void test_qsort_s(void)
         ok(tab[i] == i, "data sorted incorrectly on position %d: %d\n", i, tab[i]);
 }
 
+static int eq_nan(double a, double b) {
+    UINT64 ai = *(UINT64*)&a;
+    UINT64 bi = *(UINT64*)&b;
+#if defined(__i386__)
+    /* On i386, msvcrt.dll doesn't preserve all the mantissa bits exactly
+     * in NANs, but e.g. the sign is preserved. */
+    UINT64 mask = 0xFFF0000000000000ULL;
+    return (ai & mask) == (bi & mask);
+#else
+    /* Ignore the quiet/signaling bit */
+    UINT64 qbit = (1ULL << 51);
+    return (ai & ~qbit) == (bi & ~qbit);
+#endif
+}
+
+static int eq_nanf(float a, float b) {
+    /* Check if NANs are equal, ignoring the quiet/signaling bit */
+    DWORD ai = *(DWORD*)&a;
+    DWORD bi = *(DWORD*)&b;
+    DWORD qbit = (1 << 22);
+    return (ai & ~qbit) == (bi & ~qbit);
+}
+
 static void test_math_functions(void)
 {
     double ret;
+    UINT64 test_nan_i = 0xFFF0000123456780ULL; // Negative snan
+    double test_nan = *(double*)&test_nan_i;
+    DWORD test_nanf_i = 0xFF801234; // Negative snan
+    float test_nanf = *(float*)&test_nanf_i;
 
     errno = 0xdeadbeef;
     p_atan(NAN);
@@ -525,6 +552,13 @@ static void test_math_functions(void)
     errno = 0xdeadbeef;
     p_exp(INFINITY);
     ok(errno == 0xdeadbeef, "errno = %d\n", errno);
+
+    ok(eq_nan(test_nan, cosh(test_nan)), "cosh not preserving nan\n");
+    ok(eq_nan(test_nan, sinh(test_nan)), "sinh not preserving nan\n");
+    ok(eq_nan(test_nan, tanh(test_nan)), "tanh not preserving nan\n");
+    ok(eq_nanf(test_nanf, coshf(test_nanf)), "coshf not preserving nan\n");
+    ok(eq_nanf(test_nanf, sinhf(test_nanf)), "sinhf not preserving nan\n");
+    ok(eq_nanf(test_nanf, tanhf(test_nanf)), "tanhf not preserving nan\n");
 }
 
 static void __cdecl test_thread_func(void *end_thread_type)
-- 
2.25.1




More information about the wine-devel mailing list