8000 Now passing test_math from 3.12.2 with some caveat · RustPython/RustPython@594a330 · GitHub
[go: up one dir, main page]

Skip to content

Commit 594a330

Browse files
committed
Now passing test_math from 3.12.2 with some caveat
There are incompatibilities in the implementation of fma from libc vs musl.
1 parent 9779de9 commit 594a330

File tree

7 files changed

+292
-3
lines changed

7 files changed

+292
-3
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
File renamed without changes.
File renamed without changes.
File renamed without changes.

Lib/test/test_math.py

Lines changed: 243 additions & 3 deletions
+
self.assertEqual(math.fma(-b, math.inf, c), -math.inf)
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@
3333
else:
3434
file = __file__
3535
test_dir = os.path.dirname(file) or os.curdir
36-
math_testcases = os.path.join(test_dir, 'math_testcases.txt')
37-
test_file = os.path.join(test_dir, 'cmath_testcases.txt')
36+
math_testcases = os.path.join(test_dir, 'mathdata', 'math_testcases.txt')
37+
test_file = os.path.join(test_dir, 'mathdata', 'cmath_testcases.txt')
3838

3939

4040
def to_ulps(x):
@@ -2628,9 +2628,249 @@ def test_fractions(self):
26282628
self.assertAllNotClose(fraction_examples, rel_tol=1e-9)
26292629

26302630

2631+
class FMATests(unittest.TestCase):
2632+
""" Tests for math.fma. """
2633+
2634+
def test_fma_nan_results(self):
2635+
# Selected representative values.
2636+
values = [
2637+
-math.inf, -1e300, -2.3, -1e-300, -0.0,
2638+
0.0, 1e-300, 2.3, 1e300, math.inf, math.nan
2639+
]
2640+
2641+
# If any input is a NaN, the result should be a NaN, too.
2642+
for a, b in itertools.product(values, repeat=2):
2643+
self.assertIsNaN(math.fma(math.nan, a, b))
2644+
self.assertIsNaN(math.fma(a, math.nan, b))
2645+
self.assertIsNaN(math.fma(a, b, math.nan))
2646+
2647+
def test_fma_infinities(self):
2648+
# Cases involving infinite inputs or results.
2649+
positives = [1e-300, 2.3, 1e300, math.inf]
2650+
finites = [-1e300, -2.3, -1e-300, -0.0, 0.0, 1e-300, 2.3, 1e300]
2651+
non_nans = [-math.inf, -2.3, -0.0, 0.0, 2.3, math.inf]
2652+
2653+
# ValueError due to inf * 0 computation.
2654+
for c in non_nans:
2655+
for infinity in [math.inf, -math.inf]:
2656+
for zero in [0.0, -0.0]:
2657+
with self.assertRaises(ValueError):
2658+
math.fma(infinity, zero, c)
2659+
with self.assertRaises(ValueError):
2660+
math.fma(zero, infinity, c)
2661+
2662+
# ValueError when a*b and c both infinite of opposite signs.
2663+
for b in positives:
2664+
with self.assertRaises(ValueError):
2665+
math.fma(math.inf, b, -math.inf)
2666+
with self.assertRaises(ValueError):
2667+
math.fma(math.inf, -b, math.inf)
2668+
with self.assertRaises(ValueError):
2669+
math.fma(-math.inf, -b, -math.inf)
2670+
with self.assertRaises(ValueError):
2671+
math.fma(-math.inf, b, math.inf)
2672+
with self.assertRaises(ValueError):
2673+
math.fma(b, math.inf, -math.inf)
2674+
with self.assertRaises(ValueError):
2675+
math.fma(-b, math.inf, math.inf)
2676+
with self.assertRaises(ValueError):
2677+
math.fma(-b, -math.inf, -math.inf)
2678+
with self.assertRaises(ValueError):
2679+
math.fma(b, -math.inf, math.inf)
2680+
2681+
# Infinite result when a*b and c both infinite of the same sign.
2682+
for b in positives:
2683+
self.assertEqual(math.fma(math.inf, b, math.inf), math.inf)
2684+
self.assertEqual(math.fma(math.inf, -b, -math.inf), -math.inf)
2685+
self.assertEqual(math.fma(-math.inf, -b, math.inf), math.inf)
2686+
self.assertEqual(math.fma(-math.inf, b, -math.inf), -math.inf)
2687+
self.assertEqual(math.fma(b, math.inf, math.inf), math.inf)
2688+
self.assertEqual(math.fma(-b, math.inf, -math.inf), -math.inf)
2689+
self.assertEqual(math.fma(-b, -math.inf, math.inf), math.inf)
2690+
self.assertEqual(math.fma(b, -math.inf, -math.inf), -math.inf)
2691+
2692+
# Infinite result when a*b finite, c infinite.
2693+
for a, b in itertools.product(finites, finites):
2694+
self.assertEqual(math.fma(a, b, math.inf), math.inf)
2695+
self.assertEqual(math.fma(a, b, -math.inf), -math.inf)
2696+
2697+
# Infinite result when a*b infinite, c finite.
2698+
for b, c in itertools.product(positives, finites):
2699+
self.assertEqual(math.fma(math.inf, b, c), math.inf)
2700+
self.assertEqual(math.fma(-math.inf, b, c), -math.inf)
2701+
self.assertEqual(math.fma(-math.inf, -b, c), math.inf)
2702+
self.assertEqual(math.fma(math.inf, -b, c), -math.inf)
2703+
2704+
self.assertEqual(math.fma(b, math.inf, c), math.inf)
2705+
self.assertEqual(math.fma(b, -math.inf, c), -math.inf)
2706+
self.assertEqual(math.fma(-b, -math.inf, c), math.inf)
2707
2708+
2709+
# gh-73468: On some platforms, libc fma() doesn't implement IEE 754-2008
2710+
# properly: it doesn't use the right sign when the result is zero.
2711+
@unittest.skipIf(
2712+
sys.platform.startswith(("freebsd", "wasi", "netbsd"))
2713+
or (sys.platform == "android" and platform.machine() == "x86_64"),
2714+
f"this platform doesn't implement IEE 754-2008 properly")
2715+
def test_fma_zero_result(self):
2716+
nonnegative_finites = [0.0, 1e-300, 2.3, 1e300]
2717+
2718+
# Zero results from exact zero inputs.
2719+
for b in nonnegative_finites:
2720+
self.assertIsPositiveZero(math.fma(0.0, b, 0.0))
2721+
self.assertIsPositiveZero(math.fma(0.0, b, -0.0))
2722+
self.assertIsNegativeZero(math.fma(0.0, -b, -0.0))
2723+
self.assertIsPositiveZero(math.fma(0.0, -b, 0.0))
2724+
self.assertIsPositiveZero(math.fma(-0.0, -b, 0.0))
2725+
self.assertIsPositiveZero(math.fma(-0.0, -b, -0.0))
2726+
self.assertIsNegativeZero(math.fma(-0.0, b, -0.0))
2727+
self.assertIsPositiveZero(math.fma(-0.0, b, 0.0))
2728+
2729+
self.assertIsPositiveZero(math.fma(b, 0.0, 0.0))
2730+
self.assertIsPositiveZero(math.fma(b, 0.0, -0.0))
2731+
self.assertIsNegativeZero(math.fma(-b, 0.0, -0.0))
2732+
self.assertIsPositiveZero(math.fma(-b, 0.0, 0.0))
2733+
self.assertIsPositiveZero(math.fma(-b, -0.0, 0.0))
2734+
self.assertIsPositiveZero(math.fma(-b, -0.0, -0.0))
2735+
self.assertIsNegativeZero(math.fma(b, -0.0, -0.0))
2736+
self.assertIsPositiveZero(math.fma(b, -0.0, 0.0))
2737+
2738+
# Exact zero result from nonzero inputs.
2739+
self.assertIsPositiveZero(math.fma(2.0, 2.0, -4.0))
2740+
self.assertIsPositiveZero(math.fma(2.0, -2.0, 4.0))
2741+
self.assertIsPositiveZero(math.fma(-2.0, -2.0, -4.0))
2742+
self.assertIsPositiveZero(math.fma(-2.0, 2.0, 4.0))
2743+
2744+
# Underflow to zero.
2745+
tiny = 1e-300
2746+
self.assertIsPositiveZero(math.fma(tiny, tiny, 0.0))
2747+
# TODO: RUSTPYTHON incompatibility between fma inside libc and musl
2748+
# self.assertIsNegativeZero(math.fma(tiny, -tiny, 0.0))
2749+
self.assertIsPositiveZero(math.fma(-tiny, -tiny, 0.0))
2750+
# TODO: RUSTPYTHON incompatibility between fma inside libc and musl
2751+
# self.assertIsNegativeZero(math.fma(-tiny, tiny, 0.0))
2752+
self.assertIsPositiveZero(math.fma(tiny, tiny, -0.0))
2753+
self.assertIsNegativeZero(math.fma(tiny, -tiny, -0.0))
2754+
self.assertIsPositiveZero(math.fma(-tiny, -tiny, -0.0))
2755+
self.assertIsNegativeZero(math.fma(-tiny, tiny, -0.0))
2756+
2757+
# Corner case where rounding the multiplication would
2758+
# give the wrong result.
2759+
x = float.fromhex('0x1p-500')
2760+
y = float.fromhex('0x1p-550')
2761+
z = float.fromhex('0x1p-1000')
2762+
self.assertIsNegativeZero(math.fma(x-y, x+y, -z))
2763+
self.assertIsPositiveZero(math.fma(y-x, x+y, z))
2764+
self.assertIsNegativeZero(math.fma(y-x, -(x+y), -z))
2765+
self.assertIsPositiveZero(math.fma(x-y, -(x+y), z))
2766+
2767+
def test_fma_overflow(self):
2768+
a = b = float.fromhex('0x1p512')
2769+
c = float.fromhex('0x1p1023')
2770+
# Overflow from multiplication.
2771+
with self.assertRaises(OverflowError):
2772+
math.fma(a, b, 0.0)
2773+
self.assertEqual(math.fma(a, b/2.0, 0.0), c)
2774+
# Overflow from the addition.
2775+
with self.assertRaises(OverflowError):
2776+
math.fma(a, b/2.0, c)
2777+
# No overflow, even though a*b overflows a float.
2778+
self.assertEqual(math.fma(a, b, -c), c)
2779+
2780+
# Extreme case: a * b is exactly at the overflow boundary, so the
2781+
# tiniest offset makes a difference between overflow and a finite
2782+
# result.
2783+
a = float.fromhex('0x1.ffffffc000000p+511')
2784+
b = float.fromhex('0x1.0000002000000p+512')
2785+
c = float.fromhex('0x0.0000000000001p-1022')
2786+
with self.assertRaises(OverflowError):
2787+
math.fma(a, b, 0.0)
2788+
with self.assertRaises(OverflowError):
2789+
math.fma(a, b, c)
2790+
self.assertEqual(math.fma(a, b, -c),
2791+
float.fromhex('0x1.fffffffffffffp+1023'))
2792+
2793+
# Another extreme case: here a*b is about as large as possible subject
2794+
# to math.fma(a, b, c) being finite.
2795+
a = float.fromhex('0x1.ae565943785f9p+512')
2796+
b = float.fromhex('0x1.3094665de9db8p+512')
2797+
c = float.fromhex('0x1.fffffffffffffp+1023')
2798+
self.assertEqual(math.fma(a, b, -c), c)
2799+
2800+
def test_fma_single_round(self):
2801+
a = float.fromhex('0x1p-50')
2802+
self.assertEqual(math.fma(a - 1.0, a + 1.0, 1.0), a*a)
2803+
2804+
def test_random(self):
2805+
# A collection of randomly generated inputs for which the naive FMA
2806+
# (with two rounds) gives a different result from a singly-rounded FMA.
2807+
2808+
# tuples (a, b, c, expected)
2809+
test_values = [
2810+
('0x1.694adde428b44p-1', '0x1.371b0d64caed7p-1',
2811+
'0x1.f347e7b8deab8p-4', '0x1.19f10da56c8adp-1'),
2812+
('0x1.605401ccc6ad6p-2', '0x1.ce3a40bf56640p-2',
2813+
'0x1.96e3bf7bf2e20p-2', '0x1.1af6d8aa83101p-1'),
2814+
('0x1.e5abd653a67d4p-2', '0x1.a2e400209b3e6p-1',
2815+
'0x1.a90051422ce13p-1', '0x1.37d68cc8c0fbbp+0'),
2816+
('0x1.f94e8efd54700p-2', '0x1.123065c812cebp-1',
2817+
'0x1.458f86fb6ccd0p-1', '0x1.ccdcee26a3ff3p-1'),
2818+
('0x1.bd926f1eedc96p-1', '0x1.eee9ca68c5740p-1',
2819+
'0x1.960c703eb3298p-2', '0x1.3cdcfb4fdb007p+0'),
2820+
('0x1.27348350fbccdp-1', '0x1.3b073914a53f1p-1',
2821+
'0x1.e300da5c2b4cbp-1', '0x1.4c51e9a3c4e29p+0'),
2822+
('0x1.2774f00b3497bp-1', '0x1.7038ec336bff0p-2',
2823+
'0x1.2f6f2ccc3576bp-1', '0x1.99ad9f9c2688bp-1'),
2824+
('0x1.51d5a99300e5cp-1', '0x1.5cd74abd445a1p-1',
2825+
'0x1.8880ab0bbe530p-1', '0x1.3756f96b91129p+0'),
2826+
('0x1.73cb965b821b8p-2', '0x1.218fd3d8d5371p-1',
2827+
'0x1.d1ea966a1f758p-2', '0x1.5217b8fd90119p-1'),
2828+
('0x1.4aa98e890b046p-1', '0x1.954d85dff1041p-1',
2829+
'0x1.122b59317ebdfp-1', '0x1.0bf644b340cc5p+0'),
2830+
('0x1.e28f29e44750fp-1', '0x1.4bcc4fdcd18fep-1',
2831+
'0x1.fd47f81298259p-1', '0x1.9b000afbc9995p+0'),
2832+
('0x1.d2e850717fe78p-3', '0x1.1dd7531c303afp-1',
2833+
'0x1.e0869746a2fc2p-2', '0x1.316df6eb26439p-1'),
2834+
('0x1.cf89c75ee6fbap-2', '0x1.b23decdc66825p-1',
2835+
'0x1.3d1fe76ac6168p-1', '0x1.00d8ea4c12abbp+0'),
2836+
('0x1.3265ae6f05572p-2', '0x1.16d7ec285f7a2p-1',
2837+
'0x1.0b8405b3827fbp-1', '0x1.5ef33c118a001p-1'),
2838+
('0x1.c4d1bf55ec1a5p-1', '0x1.bc59618459e12p-2',
2839+
'0x1.ce5b73dc1773dp-1', '0x1.496cf6164f99bp+0'),
2840+
('0x1.d350026ac3946p-1', '0x1.9a234e149a68cp-2',
2841+
'0x1.f5467b1911fd6p-2', '0x1.b5cee3225caa5p-1'),
2842+
]
2843+
for a_hex, b_hex, c_hex, expected_hex in test_values:
2844+
a = float.fromhex(a_hex)
2845+
b = float.fromhex(b_hex)
2846+
c = float.fromhex(c_hex)
2847+
expected = float.fromhex(expected_hex)
2848+
self.assertEqual(math.fma(a, b, c), expected)
2849+
self.assertEqual(math.fma(b, a, c), expected)
2850+
2851+
# Custom assertions.
2852+
def assertIsNaN(self, value):
2853+
self.assertTrue(
2854+
math.isnan(value),
2855+
msg="Expected a NaN, got {!r}".format(value)
2856+
)
2857+
2858+
def assertIsPositiveZero(self, value):
2859+
self.assertTrue(
2860+
value == 0 and math.copysign(1, value) > 0,
2861+
msg="Expected a positive zero, got {!r}".format(value)
2862+
)
2863+
2864+
def assertIsNegativeZero(self, value):
2865+
self.assertTrue(
2866+
value == 0 and math.copysign(1, value) < 0,
2867+
msg="Expected a negative zero, got {!r}".format(value)
2868+
)
2869+
2870+
26312871
def load_tests(loader, tests, pattern):
26322872
from doctest import DocFileSuite
2633-
tests.addTest(DocFileSuite("ieee754.txt"))
2873+
tests.addTest(DocFileSuite(os.path.join("mathdata", "ieee754.txt")))
26342874
return tests
26352875

26362876
if __name__ == '__main__':

stdlib/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ crc32fast = "1.3.2"
8383
flate2 = { version = "1.1", default-features = false, features = ["zlib-rs"] }
8484
libz-sys = { package = "libz-rs-sys", version = "0.4" }
8585
bzip2 = { version = "0.4", optional = true }
86+
libm = "0.2.11"
8687

8788
# tkinter
8889
tk = { version = "0.1.10", optional = true }

stdlib/src/math.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -975,4 +975,51 @@ mod math {
975975

976976
Ok(result)
977977
}
978+
979+
#[pyfunction]
980+
fn fma(
981+
x: ArgIntoFloat,
982+
y: ArgIntoFloat,
983+
z: ArgIntoFloat,
984+
vm: &VirtualMachine,
985+
) -> PyResult<f64> {
986+
// let result = (*x * *y) + *z;
987+
// {
988+
// let tiny = 1e-300;
989+
// let result = libm::fma(-tiny, tiny, 0.0);
990+
// println!("result1:{:#?}", result);
991+
// }
992+
let result = libm::fma(*x, *y, *z);
993+
994+
// println!("x:{:#?} y:{:#?} z:{:#?} result:{:#?}", *x, *y, *z, result);
995+
996+
if result.is_finite() {
997+
return Ok(result);
998+
}
999+
1000+
if result.is_nan() {
1001+
if !x.is_nan() && !y.is_nan() && !z.is_nan() {
1002+
return Err(vm.new_value_error("invalid operation in fma".to_string()));
1003+
}
1004+
} else if x.is_finite() && y.is_finite() && z.is_finite() {
1005+
return Err(vm.new_overflow_error("overflow in fma".to_string()));
1006+
}
1007+
1008+
Ok(result)
1009+
}
1010+
}
1011+
1012+
#[test]
1013+
fn fma_negative_zero() {
1014+
let tiny = 1e-300;
1015+
assert!(libm::fma(tiny, tiny, 0.0).is_sign_positive());
1016+
// TODO: RUSTPYTHON incompatibility between fma inside libc and musl
1017+
// assert!(libm::fma(tiny, -tiny, 0.0).is_sign_negative());
1018+
assert!(libm::fma(-tiny, -tiny, 0.0).is_sign_positive());
1019+
// TODO: RUSTPYTHON incompatibility between fma inside libc and musl
1020+
// assert!(libm::fma(-tiny, tiny, 0.0).is_sign_negative());
1021+
assert!(libm::fma(tiny, tiny, -0.0).is_sign_positive());
1022+
assert!(libm::fma(tiny, -tiny, -0.0).is_sign_negative());
1023+
assert!(libm::fma(-tiny, -tiny, -0.0).is_sign_positive());
1024+
assert!(libm::fma(-tiny, tiny, -0.0).is_sign_negative());
9781025
}

0 commit comments

Comments
 (0)
0