diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 3e080624c8..6c634ccd81 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -854,7 +854,7 @@ SOCK_MAX_SIZE = 16 * 1024 * 1024 + 1 # requires_IEEE_754 = unittest.skipUnless( # float.__getformat__("double").startswith("IEEE"), # "test requires IEEE 754 doubles") -requires_IEEE_754 = unittest.skip("TODO: RustPython doesn't run IEEE754 tests") +requires_IEEE_754 = unittest.skipIf(False, "RustPython always has IEEE 754 floating point numbers") requires_zlib = unittest.skipUnless(zlib, 'requires zlib') diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index 91a262e2e5..6762ff44c0 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -2,7 +2,7 @@ # Python test set -- math module # XXXX Should not do tests around zero only -from test.support import run_unittest, verbose#, requires_IEEE_754 # TODO: RUSTPYTHON, commented due to import error +from test.support import run_unittest, verbose, requires_IEEE_754 from test import support import unittest import itertools @@ -442,7 +442,6 @@ class MathTests(unittest.TestCase): # similarly, copysign(2., NAN) could be 2. or -2. self.assertEqual(abs(math.copysign(2., NAN)), 2.) - @unittest.skip('TODO: RUSTPYTHON') def testCos(self): self.assertRaises(TypeError, math.cos) self.ftest('cos(-pi/2)', math.cos(-math.pi/2), 0, abs_tol=math.ulp(1)) @@ -1475,7 +1474,6 @@ class MathTests(unittest.TestCase): self.assertRaises(ValueError, math.tan, NINF) self.assertTrue(math.isnan(math.tan(NAN))) - @unittest.skip('TODO: RUSTPYTHON') def testTanh(self): self.assertRaises(TypeError, math.tanh) self.ftest('tanh(0)', math.tanh(0), 0) @@ -1948,75 +1946,76 @@ class MathTests(unittest.TestCase): self.assertIs(type(comb(IntSubclass(5), IntSubclass(k))), int) self.assertIs(type(comb(MyIndexable(5), MyIndexable(k))), int) - # TODO: RUSTPYTHON - # @requires_IEEE_754 - # def test_nextafter(self): - # # around 2^52 and 2^63 - # self.assertEqual(math.nextafter(4503599627370496.0, -INF), - # 4503599627370495.5) - # self.assertEqual(math.nextafter(4503599627370496.0, INF), - # 4503599627370497.0) - # self.assertEqual(math.nextafter(9223372036854775808.0, 0.0), - # 9223372036854774784.0) - # self.assertEqual(math.nextafter(-9223372036854775808.0, 0.0), - # -9223372036854774784.0) + @requires_IEEE_754 + def test_nextafter(self): + # around 2^52 and 2^63 + self.assertEqual(math.nextafter(4503599627370496.0, -INF), + 4503599627370495.5) + self.assertEqual(math.nextafter(4503599627370496.0, INF), + 4503599627370497.0) + self.assertEqual(math.nextafter(9223372036854775808.0, 0.0), + 9223372036854774784.0) + self.assertEqual(math.nextafter(-9223372036854775808.0, 0.0), + -9223372036854774784.0) - # # around 1.0 - # self.assertEqual(math.nextafter(1.0, -INF), - # float.fromhex('0x1.fffffffffffffp-1')) - # self.assertEqual(math.nextafter(1.0, INF), - # float.fromhex('0x1.0000000000001p+0')) + # around 1.0 + self.assertEqual(math.nextafter(1.0, -INF), + float.fromhex('0x1.fffffffffffffp-1')) + self.assertEqual(math.nextafter(1.0, INF), + float.fromhex('0x1.0000000000001p+0')) - # # x == y: y is returned - # self.assertEqual(math.nextafter(2.0, 2.0), 2.0) - # self.assertEqualSign(math.nextafter(-0.0, +0.0), +0.0) - # self.assertEqualSign(math.nextafter(+0.0, -0.0), -0.0) + # x == y: y is returned + self.assertEqual(math.nextafter(2.0, 2.0), 2.0) + self.assertEqualSign(math.nextafter(-0.0, +0.0), +0.0) + self.assertEqualSign(math.nextafter(+0.0, -0.0), -0.0) - # # around 0.0 - # smallest_subnormal = sys.float_info.min * sys.float_info.epsilon - # self.assertEqual(math.nextafter(+0.0, INF), smallest_subnormal) - # self.assertEqual(math.nextafter(-0.0, INF), smallest_subnormal) - # self.assertEqual(math.nextafter(+0.0, -INF), -smallest_subnormal) - # self.assertEqual(math.nextafter(-0.0, -INF), -smallest_subnormal) - # self.assertEqualSign(math.nextafter(smallest_subnormal, +0.0), +0.0) - # self.assertEqualSign(math.nextafter(-smallest_subnormal, +0.0), -0.0) - # self.assertEqualSign(math.nextafter(smallest_subnormal, -0.0), +0.0) - # self.assertEqualSign(math.nextafter(-smallest_subnormal, -0.0), -0.0) + # around 0.0 + # TODO: RUSTPYTHON + # smallest_subnormal = sys.float_info.min * sys.float_info.epsilon + # self.assertEqual(math.nextafter(+0.0, INF), smallest_subnormal) + # self.assertEqual(math.nextafter(-0.0, INF), smallest_subnormal) + # self.assertEqual(math.nextafter(+0.0, -INF), -smallest_subnormal) + # self.assertEqual(math.nextafter(-0.0, -INF), -smallest_subnormal) + # self.assertEqualSign(math.nextafter(smallest_subnormal, +0.0), +0.0) + # self.assertEqualSign(math.nextafter(-smallest_subnormal, +0.0), -0.0) + # self.assertEqualSign(math.nextafter(smallest_subnormal, -0.0), +0.0) + # self.assertEqualSign(math.nextafter(-smallest_subnormal, -0.0), -0.0) - # # around infinity - # largest_normal = sys.float_info.max - # self.assertEqual(math.nextafter(INF, 0.0), largest_normal) - # self.assertEqual(math.nextafter(-INF, 0.0), -largest_normal) - # self.assertEqual(math.nextafter(largest_normal, INF), INF) - # self.assertEqual(math.nextafter(-largest_normal, -INF), -INF) + # around infinity + largest_normal = sys.float_info.max + self.assertEqual(math.nextafter(INF, 0.0), largest_normal) + self.assertEqual(math.nextafter(-INF, 0.0), -largest_normal) + self.assertEqual(math.nextafter(largest_normal, INF), INF) + self.assertEqual(math.nextafter(-largest_normal, -INF), -INF) - # # NaN - # self.assertIsNaN(math.nextafter(NAN, 1.0)) - # self.assertIsNaN(math.nextafter(1.0, NAN)) - # self.assertIsNaN(math.nextafter(NAN, NAN)) + # NaN + self.assertIsNaN(math.nextafter(NAN, 1.0)) + self.assertIsNaN(math.nextafter(1.0, NAN)) + self.assertIsNaN(math.nextafter(NAN, NAN)) - # @requires_IEEE_754 - # def test_ulp(self): - # self.assertEqual(math.ulp(1.0), sys.float_info.epsilon) - # # use int ** int rather than float ** int to not rely on pow() accuracy - # self.assertEqual(math.ulp(2 ** 52), 1.0) - # self.assertEqual(math.ulp(2 ** 53), 2.0) - # self.assertEqual(math.ulp(2 ** 64), 4096.0) + @requires_IEEE_754 + def test_ulp(self): + self.assertEqual(math.ulp(1.0), sys.float_info.epsilon) + # use int ** int rather than float ** int to not rely on pow() accuracy + self.assertEqual(math.ulp(2 ** 52), 1.0) + self.assertEqual(math.ulp(2 ** 53), 2.0) + self.assertEqual(math.ulp(2 ** 64), 4096.0) - # # min and max - # self.assertEqual(math.ulp(0.0), - # sys.float_info.min * sys.float_info.epsilon) - # self.assertEqual(math.ulp(FLOAT_MAX), - # FLOAT_MAX - math.nextafter(FLOAT_MAX, -INF)) + # min and max + # TODO: RUSTPYTHON + # self.assertEqual(math.ulp(0.0), + # sys.float_info.min * sys.float_info.epsilon) + self.assertEqual(math.ulp(FLOAT_MAX), + FLOAT_MAX - math.nextafter(FLOAT_MAX, -INF)) - # # special cases - # self.assertEqual(math.ulp(INF), INF) - # self.assertIsNaN(math.ulp(math.nan)) + # special cases + self.assertEqual(math.ulp(INF), INF) + self.assertIsNaN(math.ulp(math.nan)) - # # negative number: ulp(-x) == ulp(x) - # for x in (0.0, 1.0, 2 ** 52, 2 ** 64, INF): - # with self.subTest(x=x): - # self.assertEqual(math.ulp(-x), math.ulp(x)) + # negative number: ulp(-x) == ulp(x) + for x in (0.0, 1.0, 2 ** 52, 2 ** 64, INF): + with self.subTest(x=x): + self.assertEqual(math.ulp(-x), math.ulp(x)) @unittest.skip('TODO: RUSTPYTHON') def test_issue39871(self): @@ -2183,4 +2182,4 @@ def test_main(): run_unittest(suite) if __name__ == '__main__': - test_main() \ No newline at end of file + test_main() diff --git a/vm/src/stdlib/math.rs b/vm/src/stdlib/math.rs index 883c08ce08..e0ef3a14ed 100644 --- a/vm/src/stdlib/math.rs +++ b/vm/src/stdlib/math.rs @@ -363,14 +363,20 @@ fn math_modf(x: IntoPyFloat) -> (f64, f64) { (x.fract(), x.trunc()) } +#[inline] #[cfg(not(target_arch = "wasm32"))] -fn math_nextafter(x: IntoPyFloat, y: IntoPyFloat) -> PyResult { +fn libc_nextafter(x: f64, y: f64) -> f64 { extern "C" { fn nextafter(x: c_double, y: c_double) -> c_double; } + unsafe { nextafter(x, y) } +} + +#[cfg(not(target_arch = "wasm32"))] +fn math_nextafter(x: IntoPyFloat, y: IntoPyFloat) -> PyResult { let x = x.to_f64(); let y = y.to_f64(); - Ok(unsafe { nextafter(x, y) }) + Ok(libc_nextafter(x, y)) } #[cfg(target_arch = "wasm32")] @@ -378,6 +384,28 @@ fn math_nextafter(_x: IntoPyFloat, _y: IntoPyFloat, vm: &VirtualMachine) -> PyRe Err(vm.new_not_implemented_error("not implemented for this platform".to_owned())) } +#[cfg(not(target_arch = "wasm32"))] +fn math_ulp(x: IntoPyFloat) -> PyResult { + let mut x = x.to_f64(); + if x.is_nan() { + return Ok(x); + } + x = x.abs(); + let mut x2 = libc_nextafter(x, f64::INFINITY); + Ok(if x2.is_infinite() { + // special case: x is the largest positive representable float + x2 = libc_nextafter(x, f64::NEG_INFINITY); + x - x2 + } else { + x2 - x + }) +} + +#[cfg(target_arch = "wasm32")] +fn math_ulp(_x: IntoPyFloat, vm: &VirtualMachine) -> PyResult { + Err(vm.new_not_implemented_error("not implemented for this platform".to_owned())) +} + fn fmod(x: f64, y: f64) -> f64 { if y.is_infinite() && x.is_finite() { return x; @@ -504,7 +532,9 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { // Factorial function "factorial" => named_function!(ctx, math, factorial), + // Floating point "nextafter" => named_function!(ctx, math, nextafter), + "ulp" => named_function!(ctx, math, ulp), // Constants: "pi" => ctx.new_float(std::f64::consts::PI), // 3.14159...