8000 gh-132876: workaround broken ldexp() on Windows 10 (#133135) · faster-cpython/cpython@cf8941c · GitHub
[go: up one dir, main page]

Skip to content

Commit cf8941c

Browse files
skirpichevtim-one
andauthored
pythongh-132876: workaround broken ldexp() on Windows 10 (python#133135)
* pythongh-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>
1 parent 0e3bc96 commit cf8941c

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
@@ -1214,6 +1214,12 @@ def testLdexp(self):
12141214
self.assertEqual(math.ldexp(NINF, n), NINF)
12151215
self.assertTrue(math.isnan(math.ldexp(NAN, n)))
12161216

1217+
@requires_IEEE_754
1218+
def testLdexp_denormal(self):
1219+
# Denormal output incorrectly rounded (truncated)
1220+
# on some Windows.
1221+
self.assertEqual(math.ldexp(6993274598585239, -1126), 1e-323)
1222+
12171223
def testLog(self):
12181224
self.assertRaises(TypeError, math.log)
12191225
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
@@ -2161,6 +2161,27 @@ math_ldexp_impl(PyObject *module, double x, PyObject *i)
21612161
} else {
21622162
errno = 0;
21632163
r = ldexp(x, (int)exp);
2164+
#ifdef _MSC_VER
2165+
if (DBL_MIN > r && r > -DBL_MIN) {
2166+
/* Denormal (or zero) results can be incorrectly rounded here (rather,
2167+
truncated). Fixed in newer versions of the C runtime, included
2168+
with Windows 11. */
2169+
int original_exp;
2170+
frexp(x, &original_exp);
2171+
if (original_exp > DBL_MIN_EXP) {
2172+
/* Shift down to the smallest normal binade. No bits lost. */
2173+
int shift = DBL_MIN_EXP - original_exp;
2174+
x = ldexp(x, shift);
2175+
exp -= shift;
2176+
}
2177+
/* Multiplying by 2**exp finishes the job, and the HW will round as
2178+
appropriate. Note: if exp < -DBL_MANT_DIG, all of x is shifted
2179+
to be < 0.5ULP of smallest denorm, so should be thrown away. If
2180+
exp is so very negative that ldexp underflows to 0, that's fine;
2181+
no need to check in advance. */
2182+
r = x*ldexp(1.0, (int)exp);
2183+
}
2184+
#endif
21642185
if (isinf(r))
21652186
errno = ERANGE;
21662187
}

0 commit comments

Comments
 (0)
0