|
28 | 28 |
|
29 | 29 | # === Helper functions and class ===
|
30 | 30 |
|
| 31 | +# Test copied from Lib/test/test_math.py |
| 32 | +# detect evidence of double-rounding: fsum is not always correctly |
| 33 | +# rounded on machines that suffer from double rounding. |
| 34 | +x, y = 1e16, 2.9999 # use temporary values to defeat peephole optimizer |
| 35 | +HAVE_DOUBLE_ROUNDING = (x + y == 1e16 + 4) |
| 36 | + |
31 | 37 | def sign(x):
|
32 | 38 | """Return -1.0 for negatives, including -0.0, otherwise +1.0."""
|
33 | 39 | return math.copysign(1, x)
|
@@ -2564,6 +2570,79 @@ def test_different_scales(self):
|
2564 | 2570 | self.assertAlmostEqual(statistics.correlation(x, y), 1)
|
2565 | 2571 | self.assertAlmostEqual(statistics.covariance(x, y), 0.1)
|
2566 | 2572 |
|
| 2573 | + def test_sqrtprod_helper_function_fundamentals(self): |
| 2574 | + # Verify that results are close to sqrt(x * y) |
| 2575 | + for i in range(100): |
| 2576 | + x = random.expovariate() |
| 2577 | + y = random.expovariate() |
| 2578 | + expected = math.sqrt(x * y) |
| 2579 | + actual = statistics._sqrtprod(x, y) |
| 2580 | + with self.subTest(x=x, y=y, expected=expected, actual=actual): |
| 2581 | + self.assertAlmostEqual(expected, actual) |
| 2582 | + |
| 2583 | + x, y, target = 0.8035720646477457, 0.7957468097636939, 0.7996498651651661 |
| 2584 | + self.assertEqual(statistics._sqrtprod(x, y), target) |
| 2585 | + self.assertNotEqual(math.sqrt(x * y), target) |
| 2586 | + |
| 2587 | + # Test that range extremes avoid underflow and overflow |
| 2588 | + smallest = sys.float_info.min * sys.float_info.epsilon |
| 2589 | + self.assertEqual(statistics._sqrtprod(smallest, smallest), smallest) |
| 2590 | + biggest = sys.float_info.max |
| 2591 | + self.assertEqual(statistics._sqrtprod(biggest, biggest), biggest) |
| 2592 | + |
| 2593 | + # Check special values and the sign of the result |
| 2594 | + special_values = [0.0, -0.0, 1.0, -1.0, 4.0, -4.0, |
| 2595 | + math.nan, -math.nan, math.inf, -math.inf] |
| 2596 | + for x, y in itertools.product(special_values, repeat=2): |
| 2597 | + try: |
| 2598 | + expected = math.sqrt(x * y) |
| 2599 | + except ValueError: |
| 2600 | + expected = 'ValueError' |
| 2601 | + try: |
| 2602 | + actual = statistics._sqrtprod(x, y) |
| 2603 | + except ValueError: |
| 2604 | + actual = 'ValueError' |
| 2605 | + with self.subTest(x=x, y=y, expected=expected, actual=actual): |
| 2606 | + if isinstance(expected, str) and expected == 'ValueError': |
| 2607 | + self.assertEqual(actual, 'ValueError') |
| 2608 | + continue |
| 2609 | + self.assertIsInstance(actual, float) |
| 2610 | + if math.isnan(expected): |
| 2611 | + self.assertTrue(math.isnan(actual)) |
| 2612 | + continue |
| 2613 | + self.assertEqual(actual, expected) |
| 2614 | + self.assertEqual(sign(actual), sign(expected)) |
| 2615 | + |
| 2616 | + @requires_IEEE_754 |
| 2617 | + @unittest.skipIf(HAVE_DOUBLE_ROUNDING, |
| 2618 | + "accuracy not guaranteed on machines with double rounding") |
| 2619 | + @support.cpython_only # Allow for a weaker sumprod() implmentation |
| 2620 | + def test_sqrtprod_helper_function_improved_accuracy(self): |
| 2621 | + # Test a known example where accuracy is improved |
| 2622 | + x, y, target = 0.8035720646477457, 0.7957468097636939, 0.7996498651651661 |
| 2623 | + self.assertEqual(statistics._sqrtprod(x, y), target) |
| 2624 | + self.assertNotEqual(math.sqrt(x * y), target) |
| 2625 | + |
| 2626 | + def reference_value(x: float, y: float) -> float: |
| 2627 | + x = decimal.Decimal(x) |
| 2628 | + y = decimal.Decimal(y) |
| 2629 | + with decimal.localcontext() as ctx: |
| 2630 | + ctx.prec = 200 |
| 2631 | + return float((x * y).sqrt()) |
| 2632 | + |
| 2633 | + # Verify that the new function with improved accuracy |
| 2634 | + # agrees with a reference value more often than old version. |
| 2635 | + new_agreements = 0 |
| 2636 | + old_agreements = 0 |
| 2637 | + for i in range(10_000): |
| 2638 | + x = random.expovariate() |
| 2639 | + y = random.expovariate() |
| 2640 | + new = statistics._sqrtprod(x, y) |
| 2641 | + old = math.sqrt(x * y) |
| 2642 | + ref = reference_value(x, y) |
| 2643 | + new_agreements += (new == ref) |
| 2644 | + old_agreements += (old == ref) |
| 2645 | + self.assertGreater(new_agreements, old_agreements) |
2567 | 2646 |
|
2568 | 2647 | def test_correlation_spearman(self):
|
2569 | 2648 | # https://statistics.laerd.com/statistical-guides/spearmans-rank-order-correlation-statistical-guide-2.php
|
|
0 commit comments