Implement math.ulp

This commit is contained in:
Árni Dagur
2020-10-21 17:51:21 +00:00
parent c319b16b62
commit da2bd5ea86
3 changed files with 96 additions and 67 deletions

View File

@@ -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')

View File

@@ -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()
test_main()

View File

@@ -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<f64> {
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<f64> {
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<f64> {
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<f64> {
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...