https://github.com/python/cpython/commit/cf8941c60356acdd00055e5583a2d64761c34af4
commit: cf8941c60356acdd00055e5583a2d64761c34af4
branch: main
author: Sergey B Kirpichev <skirpic...@gmail.com>
committer: tim-one <tim.pet...@gmail.com>
date: 2025-05-25T21:44:33-05:00
summary:

gh-132876: workaround broken ldexp() on Windows 10 (#133135)

* 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.pet...@gmail.com>

files:
A Misc/NEWS.d/next/Library/2025-04-29-11-48-46.gh-issue-132876.lyTQGZ.rst
M Lib/test/test_math.py
M Modules/mathmodule.c

diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py
index 913a60bf9e04e3..d14336f8bac498 100644
--- a/Lib/test/test_math.py
+++ b/Lib/test/test_math.py
@@ -1214,6 +1214,12 @@ def testLdexp(self):
             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
index 00000000000000..cb3ca3321e3d26
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-04-29-11-48-46.gh-issue-132876.lyTQGZ.rst
@@ -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.
diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c
index 40abd69f0a6600..71d9c1387f5780 100644
--- a/Modules/mathmodule.c
+++ b/Modules/mathmodule.c
@@ -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;
     }

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: arch...@mail-archive.com

Reply via email to