https://github.com/python/cpython/commit/a61b19b2bc3b5252163c88d78815fb7903149734
commit: a61b19b2bc3b5252163c88d78815fb7903149734
branch: main
author: Michael <[email protected]>
committer: rhettinger <[email protected]>
date: 2026-11-08T11:09:03-06:00
summary:

gh-161259: Fix statistics._sqrtprod() for subnormal products (gh-161260)

files:
A Misc/NEWS.d/next/Library/2026-11-03-18-56-28.gh-issue-161259.iyZz6y.rst
M Lib/statistics.py
M Lib/test/test_statistics.py

diff --git a/Lib/statistics.py b/Lib/statistics.py
index eee3e6eb94e990..aaf3799716d8fb 100644
--- a/Lib/statistics.py
+++ b/Lib/statistics.py
@@ -1776,9 +1776,11 @@ def _sqrtprod(x: float, y: float) -> float:
             return _sqrtprod(scale * x, scale * y) / scale
         return h
 
-    if not h:
+    subnormal_cutoff = 2.0 ** -485  # sqrt(sys.float_info.min / 
sys.float_info.epsilon)
+    if h < subnormal_cutoff:
+        # x * y is subnormal or underflowed, or the correction term
+        # below would be. Rescale non-zero inputs.
         if x and y:
-            # Non-zero inputs underflowed, so scale up, and recompute.
             # Scale:  1 / sqrt(sys.float_info.min * sys.float_info.epsilon)
             scale = 2.0 ** 537
             return _sqrtprod(scale * x, scale * y) / scale
diff --git a/Lib/test/test_statistics.py b/Lib/test/test_statistics.py
index 700c5ac304f717..266d92828fe154 100644
--- a/Lib/test/test_statistics.py
+++ b/Lib/test/test_statistics.py
@@ -2790,6 +2790,66 @@ def test_sqrtprod_helper_function_fundamentals(self):
                 self.assertEqual(actual, expected)
                 self.assertEqual(sign(actual), sign(expected))
 
+    @requires_IEEE_754
+    def test_sqrtprod_helper_function_subnormal_products(self):
+        # gh-161259: accuracy was lost when the product or the correction
+        # term was subnormal. The reference scales the inputs by an exact
+        # power of two so that the product is a normal float.
+        scale = 2.0 ** 600
+        for x, y in [
+            (2e-200, 2e-124),
+            (1e-160, 3e-150),
+            (3e-170, 7e-140),
+            (1e-160, 1e-135),
+        ]:
+            expected = math.sqrt((scale * x) * (scale * y)) / scale
+            actual = statistics._sqrtprod(x, y)
+            with self.subTest(x=x, y=y, expected=expected, actual=actual):
+                self.assertAlmostEqual(actual / expected, 1.0, delta=2**-50)
+
+    @requires_IEEE_754
+    def test_sqrtprod_helper_function_subnormal_boundary(self):
+        # sqrt(3 * 7) * 2**e straddles the 2**-485 cutoff where the
+        # correction term can become subnormal.
+        scale = 2.0 ** 600
+        for e in (-488, -487):
+            x = 3.0 * 2.0 ** e
+            y = 7.0 * 2.0 ** e
+            expected = math.sqrt((scale * x) * (scale * y)) / scale
+            actual = statistics._sqrtprod(x, y)
+            with self.subTest(e=e, expected=expected, actual=actual):
+                self.assertAlmostEqual(actual / expected, 1.0, delta=2**-50)
+
+        cutoff = 2.0 ** -485
+        self.assertEqual(statistics._sqrtprod(cutoff, cutoff), cutoff)
+
+    @requires_IEEE_754
+    def test_correlation_tiny_inputs(self):
+        # gh-161259: correlation() is scale-invariant, even when the
+        # intermediate sums of squares are tiny.
+        x = [1e-100, 2e-100, 3e-100]
+        y = [1e-62, 2e-62, 3e-62]
+        self.assertAlmostEqual(statistics.correlation(x, y), 1.0)
+
+    @requires_IEEE_754
+    def test_correlation_asymmetric_scales(self):
+        for a, b in [
+            (2.0 ** 50, 2.0 ** -537),
+            (1.3 * 2.0 ** -260, 2.7 * 2.0 ** -270),
+        ]:
+            x = [-a, 0.0, a]
+            y = [0.0, -b, b]
+            with self.subTest(a=a, b=b):
+                self.assertAlmostEqual(statistics.correlation(x, y), 0.5,
+                                       delta=2**-50)
+
+    @requires_IEEE_754
+    def test_correlation_tiny_products_max_recursion(self):
+        # Make both sums of squares round to the smallest subnormal.
+        a = 2.0 ** -537
+        x = [0.0, 0.0, 0.0, a]
+        self.assertEqual(statistics.correlation(x, x), 1.0)
+
     @requires_IEEE_754
     @skip_if_double_rounding
     @support.cpython_only    # Allow for a weaker sumprod() implementation
diff --git 
a/Misc/NEWS.d/next/Library/2026-11-03-18-56-28.gh-issue-161259.iyZz6y.rst 
b/Misc/NEWS.d/next/Library/2026-11-03-18-56-28.gh-issue-161259.iyZz6y.rst
new file mode 100644
index 00000000000000..400f6e5b716941
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-11-03-18-56-28.gh-issue-161259.iyZz6y.rst
@@ -0,0 +1,3 @@
+Improved the accuracy of :func:`statistics.correlation` for very small
+inputs, where the product of the sums of squares is subnormal rather
+than zero.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to