Skip to content

Commit e483dcf

Browse files
skirpichevtim-one
andauthored
[3.13] gh-132876: workaround broken ldexp() on Windows 10 (GH-133135) (#134685)
* gh-132876: workaround broken ldexp() on Windows 10 ldexp() fails to round subnormal results before Windows 11, so hide their bug. (cherry picked from commit cf8941c) Co-authored-by: Tim Peters <[email protected]>
1 parent f331d03 commit e483dcf

File tree

3 files changed

+31
-0
lines changed

3 files changed

+31
-0
lines changed

Lib/test/test_math.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,6 +1202,12 @@ def testLdexp(self):
12021202
self.assertEqual(math.ldexp(NINF, n), NINF)
12031203
self.assertTrue(math.isnan(math.ldexp(NAN, n)))
12041204

1205+
@requires_IEEE_754
1206+
def testLdexp_denormal(self):
1207+
# Denormal output incorrectly rounded (truncated)
1208+
# on some Windows.
1209+
self.assertEqual(math.ldexp(6993274598585239, -1126), 1e-323)
1210+
12051211
def testLog(self):
12061212
self.assertRaises(TypeError, math.log)
12071213
self.assertRaises(TypeError, math.log, 1, 2, 3)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
``ldexp()`` on Windows doesn't round subnormal results before Windows 11,
2+
but should. Python's :func:`math.ldexp` wrapper now does round them, so
3+
results may change slightly, in rare cases of very small results, on
4+
Windows versions before 11.

Modules/mathmodule.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2166,6 +2166,27 @@ math_ldexp_impl(PyObject *module, double x, PyObject *i)
21662166
} else {
21672167
errno = 0;
21682168
r = ldexp(x, (int)exp);
2169+
#ifdef _MSC_VER
2170+
if (DBL_MIN > r && r > -DBL_MIN) {
2171+
/* Denormal (or zero) results can be incorrectly rounded here (rather,
2172+
truncated). Fixed in newer versions of the C runtime, included
2173+
with Windows 11. */
2174+
int original_exp;
2175+
frexp(x, &original_exp);
2176+
if (original_exp > DBL_MIN_EXP) {
2177+
/* Shift down to the smallest normal binade. No bits lost. */
2178+
int shift = DBL_MIN_EXP - original_exp;
2179+
x = ldexp(x, shift);
2180+
exp -= shift;
2181+
}
2182+
/* Multiplying by 2**exp finishes the job, and the HW will round as
2183+
appropriate. Note: if exp < -DBL_MANT_DIG, all of x is shifted
2184+
to be < 0.5ULP of smallest denorm, so should be thrown away. If
2185+
exp is so very negative that ldexp underflows to 0, that's fine;
2186+
no need to check in advance. */
2187+
r = x*ldexp(1.0, (int)exp);
2188+
}
2189+
#endif
21692190
if (Py_IS_INFINITY(r))
21702191
errno = ERANGE;
21712192
}

0 commit comments

Comments
 (0)