]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-132876: workaround broken ldexp() on Windows 10 (#133135)
authorSergey B Kirpichev <skirpichev@gmail.com>
Mon, 26 May 2025 02:44:33 +0000 (05:44 +0300)
committerGitHub <noreply@github.com>
Mon, 26 May 2025 02:44:33 +0000 (21:44 -0500)
* gh-132876: workaround broken ldexp() on Windows 10

ldexp() fails to round subnormal results before Windows 11,
so hide their bug.

Co-authored-by: Tim Peters <tim.peters@gmail.com>
Lib/test/test_math.py
Misc/NEWS.d/next/Library/2025-04-29-11-48-46.gh-issue-132876.lyTQGZ.rst [new file with mode: 0644]
Modules/mathmodule.c

index 913a60bf9e04e3338c7eed46d8ce763efac6079c..d14336f8bac49822441c07dd87e168764e5e3217 100644 (file)
@@ -1214,6 +1214,12 @@ class MathTests(unittest.TestCase):
             self.assertEqual(math.ldexp(NINF, n), NINF)
             self.assertTrue(math.isnan(math.ldexp(NAN, n)))
 
+    @requires_IEEE_754
+    def testLdexp_denormal(self):
+        # Denormal output incorrectly rounded (truncated)
+        # on some Windows.
+        self.assertEqual(math.ldexp(6993274598585239, -1126), 1e-323)
+
     def testLog(self):
         self.assertRaises(TypeError, math.log)
         self.assertRaises(TypeError, math.log, 1, 2, 3)
diff --git a/Misc/NEWS.d/next/Library/2025-04-29-11-48-46.gh-issue-132876.lyTQGZ.rst b/Misc/NEWS.d/next/Library/2025-04-29-11-48-46.gh-issue-132876.lyTQGZ.rst
new file mode 100644 (file)
index 0000000..cb3ca33
--- /dev/null
@@ -0,0 +1,4 @@
+``ldexp()`` on Windows doesn't round subnormal results before Windows 11,
+but should.  Python's :func:`math.ldexp` wrapper now does round them, so
+results may change slightly, in rare cases of very small results, on
+Windows versions before 11.
index 40abd69f0a660072e41b8d5eca13f22fd2d8afe1..71d9c1387f578048e1bebbcddfd45d473d77bfb3 100644 (file)
@@ -2161,6 +2161,27 @@ math_ldexp_impl(PyObject *module, double x, PyObject *i)
     } else {
         errno = 0;
         r = ldexp(x, (int)exp);
+#ifdef _MSC_VER
+        if (DBL_MIN > r && r > -DBL_MIN) {
+            /* Denormal (or zero) results can be incorrectly rounded here (rather,
+               truncated).  Fixed in newer versions of the C runtime, included
+               with Windows 11. */
+            int original_exp;
+            frexp(x, &original_exp);
+            if (original_exp > DBL_MIN_EXP) {
+                /* Shift down to the smallest normal binade.  No bits lost. */
+                int shift = DBL_MIN_EXP - original_exp;
+                x = ldexp(x, shift);
+                exp -= shift;
+            }
+            /* Multiplying by 2**exp finishes the job, and the HW will round as
+               appropriate.  Note: if exp < -DBL_MANT_DIG, all of x is shifted
+               to be < 0.5ULP of smallest denorm, so should be thrown away.  If
+               exp is so very negative that ldexp underflows to 0, that's fine;
+               no need to check in advance. */
+            r = x*ldexp(1.0, (int)exp);
+        }
+#endif
         if (isinf(r))
             errno = ERANGE;
     }