Skip to content

Commit 5abb7b4

Browse files
committed
gh-161259: Fix statistics._sqrtprod() for subnormal products
1 parent 99799a9 commit 5abb7b4

3 files changed

Lines changed: 29 additions & 2 deletions

File tree

‎Lib/statistics.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1776,9 +1776,10 @@ def _sqrtprod(x: float, y: float) -> float:
17761776
return _sqrtprod(scale * x, scale * y) / scale
17771777
return h
17781778

1779-
if not h:
1779+
if h < 2.0 ** -485:
1780+
# x * y is subnormal or underflowed, or the correction term
1781+
# below would be. Rescale non-zero inputs.
17801782
if x and y:
1781-
# Non-zero inputs underflowed, so scale up, and recompute.
17821783
# Scale: 1 / sqrt(sys.float_info.min * sys.float_info.epsilon)
17831784
scale = 2.0 ** 537
17841785
return _sqrtprod(scale * x, scale * y) / scale

‎Lib/test/test_statistics.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2789,6 +2789,29 @@ def test_sqrtprod_helper_function_fundamentals(self):
27892789
continue
27902790
self.assertEqual(actual, expected)
27912791
self.assertEqual(sign(actual), sign(expected))
2792+
2793+
def test_sqrtprod_helper_function_subnormal_products(self):
2794+
# gh-161259: accuracy was lost when x * y is subnormal.
2795+
# The reference scales the inputs by an exact power of two so that
2796+
# the product is a normal float.
2797+
scale = 2.0**600
2798+
for x, y in [
2799+
(2e-200, 2e-124),
2800+
(1e-160, 3e-150),
2801+
(3e-170, 7e-140),
2802+
(1e-160, 1e-135),
2803+
]:
2804+
expected = math.sqrt((scale * x) * (scale * y)) / scale
2805+
actual = statistics._sqrtprod(x, y)
2806+
with self.subTest(x=x, y=y, expected=expected, actual=actual):
2807+
self.assertAlmostEqual(actual / expected, 1.0, delta=2**-50)
2808+
2809+
def test_correlation_tiny_inputs(self):
2810+
# gh-161259: correlation() is scale-invariant, even when the
2811+
# intermediate sums of squares are tiny.
2812+
x = [1e-100, 2e-100, 3e-100]
2813+
y = [1e-62, 2e-62, 3e-62]
2814+
self.assertAlmostEqual(statistics.correlation(x, y), 1.0)
27922815

27932816
@requires_IEEE_754
27942817
@skip_if_double_rounding
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :func:`statistics.correlation` returning inaccurate results for very
2+
small inputs, where the product of the sums of squares is subnormal rather
3+
than zero.

0 commit comments

Comments
 (0)