Skip to content

Commit da2bd5e

Browse files
committed
Implement math.ulp
1 parent c319b16 commit da2bd5e

3 files changed

Lines changed: 105 additions & 76 deletions

File tree

Lib/test/support/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -854,7 +854,7 @@ def dec(*args, **kwargs):
854854
# requires_IEEE_754 = unittest.skipUnless(
855855
# float.__getformat__("double").startswith("IEEE"),
856856
# "test requires IEEE 754 doubles")
857-
requires_IEEE_754 = unittest.skip("TODO: RustPython doesn't run IEEE754 tests")
857+
requires_IEEE_754 = unittest.skipIf(False, "RustPython always has IEEE 754 floating point numbers")
858858

859859
requires_zlib = unittest.skipUnless(zlib, 'requires zlib')
860860

Lib/test/test_math.py

Lines changed: 72 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Python test set -- math module
33
# XXXX Should not do tests around zero only
44

5-
from test.support import run_unittest, verbose#, requires_IEEE_754 # TODO: RUSTPYTHON, commented due to import error
5+
from test.support import run_unittest, verbose, requires_IEEE_754
66
from test import support
77
import unittest
88
import itertools
@@ -442,7 +442,6 @@ def testCopysign(self):
442442
# similarly, copysign(2., NAN) could be 2. or -2.
443443
self.assertEqual(abs(math.copysign(2., NAN)), 2.)
444444

445-
@unittest.skip('TODO: RUSTPYTHON')
446445
def testCos(self):
447446
self.assertRaises(TypeError, math.cos)
448447
self.ftest('cos(-pi/2)', math.cos(-math.pi/2), 0, abs_tol=math.ulp(1))
@@ -1475,7 +1474,6 @@ def testTan(self):
14751474
self.assertRaises(ValueError, math.tan, NINF)
14761475
self.assertTrue(math.isnan(math.tan(NAN)))
14771476

1478-
@unittest.skip('TODO: RUSTPYTHON')
14791477
def testTanh(self):
14801478
self.assertRaises(TypeError, math.tanh)
14811479
self.ftest('tanh(0)', math.tanh(0), 0)
@@ -1948,75 +1946,76 @@ def testComb(self):
19481946
self.assertIs(type(comb(IntSubclass(5), IntSubclass(k))), int)
19491947
self.assertIs(type(comb(MyIndexable(5), MyIndexable(k))), int)
19501948

1951-
# TODO: RUSTPYTHON
1952-
# @requires_IEEE_754
1953-
# def test_nextafter(self):
1954-
# # around 2^52 and 2^63
1955-
# self.assertEqual(math.nextafter(4503599627370496.0, -INF),
1956-
# 4503599627370495.5)
1957-
# self.assertEqual(math.nextafter(4503599627370496.0, INF),
1958-
# 4503599627370497.0)
1959-
# self.assertEqual(math.nextafter(9223372036854775808.0, 0.0),
1960-
# 9223372036854774784.0)
1961-
# self.assertEqual(math.nextafter(-9223372036854775808.0, 0.0),
1962-
# -9223372036854774784.0)
1963-
1964-
# # around 1.0
1965-
# self.assertEqual(math.nextafter(1.0, -INF),
1966-
# float.fromhex('0x1.fffffffffffffp-1'))
1967-
# self.assertEqual(math.nextafter(1.0, INF),
1968-
# float.fromhex('0x1.0000000000001p+0'))
1969-
1970-
# # x == y: y is returned
1971-
# self.assertEqual(math.nextafter(2.0, 2.0), 2.0)
1972-
# self.assertEqualSign(math.nextafter(-0.0, +0.0), +0.0)
1973-
# self.assertEqualSign(math.nextafter(+0.0, -0.0), -0.0)
1974-
1975-
# # around 0.0
1976-
# smallest_subnormal = sys.float_info.min * sys.float_info.epsilon
1977-
# self.assertEqual(math.nextafter(+0.0, INF), smallest_subnormal)
1978-
# self.assertEqual(math.nextafter(-0.0, INF), smallest_subnormal)
1979-
# self.assertEqual(math.nextafter(+0.0, -INF), -smallest_subnormal)
1980-
# self.assertEqual(math.nextafter(-0.0, -INF), -smallest_subnormal)
1981-
# self.assertEqualSign(math.nextafter(smallest_subnormal, +0.0), +0.0)
1982-
# self.assertEqualSign(math.nextafter(-smallest_subnormal, +0.0), -0.0)
1983-
# self.assertEqualSign(math.nextafter(smallest_subnormal, -0.0), +0.0)
1984-
# self.assertEqualSign(math.nextafter(-smallest_subnormal, -0.0), -0.0)
1985-
1986-
# # around infinity
1987-
# largest_normal = sys.float_info.max
1988-
# self.assertEqual(math.nextafter(INF, 0.0), largest_normal)
1989-
# self.assertEqual(math.nextafter(-INF, 0.0), -largest_normal)
1990-
# self.assertEqual(math.nextafter(largest_normal, INF), INF)
1991-
# self.assertEqual(math.nextafter(-largest_normal, -INF), -INF)
1992-
1993-
# # NaN
1994-
# self.assertIsNaN(math.nextafter(NAN, 1.0))
1995-
# self.assertIsNaN(math.nextafter(1.0, NAN))
1996-
# self.assertIsNaN(math.nextafter(NAN, NAN))
1997-
1998-
# @requires_IEEE_754
1999-
# def test_ulp(self):
2000-
# self.assertEqual(math.ulp(1.0), sys.float_info.epsilon)
2001-
# # use int ** int rather than float ** int to not rely on pow() accuracy
2002-
# self.assertEqual(math.ulp(2 ** 52), 1.0)
2003-
# self.assertEqual(math.ulp(2 ** 53), 2.0)
2004-
# self.assertEqual(math.ulp(2 ** 64), 4096.0)
2005-
2006-
# # min and max
2007-
# self.assertEqual(math.ulp(0.0),
2008-
# sys.float_info.min * sys.float_info.epsilon)
2009-
# self.assertEqual(math.ulp(FLOAT_MAX),
2010-
# FLOAT_MAX - math.nextafter(FLOAT_MAX, -INF))
2011-
2012-
# # special cases
2013-
# self.assertEqual(math.ulp(INF), INF)
2014-
# self.assertIsNaN(math.ulp(math.nan))
2015-
2016-
# # negative number: ulp(-x) == ulp(x)
2017-
# for x in (0.0, 1.0, 2 ** 52, 2 ** 64, INF):
2018-
# with self.subTest(x=x):
2019-
# self.assertEqual(math.ulp(-x), math.ulp(x))
1949+
@requires_IEEE_754
1950+
def test_nextafter(self):
1951+
# around 2^52 and 2^63
1952+
self.assertEqual(math.nextafter(4503599627370496.0, -INF),
1953+
4503599627370495.5)
1954+
self.assertEqual(math.nextafter(4503599627370496.0, INF),
1955+
4503599627370497.0)
1956+
self.assertEqual(math.nextafter(9223372036854775808.0, 0.0),
1957+
9223372036854774784.0)
1958+
self.assertEqual(math.nextafter(-9223372036854775808.0, 0.0),
1959+
-9223372036854774784.0)
1960+
1961+
# around 1.0
1962+
self.assertEqual(math.nextafter(1.0, -INF),
1963+
float.fromhex('0x1.fffffffffffffp-1'))
1964+
self.assertEqual(math.nextafter(1.0, INF),
1965+
float.fromhex('0x1.0000000000001p+0'))
1966+
1967+
# x == y: y is returned
1968+
self.assertEqual(math.nextafter(2.0, 2.0), 2.0)
1969+
self.assertEqualSign(math.nextafter(-0.0, +0.0), +0.0)
1970+
self.assertEqualSign(math.nextafter(+0.0, -0.0), -0.0)
1971+
1972+
# around 0.0
1973+
# TODO: RUSTPYTHON
1974+
# smallest_subnormal = sys.float_info.min * sys.float_info.epsilon
1975+
# self.assertEqual(math.nextafter(+0.0, INF), smallest_subnormal)
1976+
# self.assertEqual(math.nextafter(-0.0, INF), smallest_subnormal)
1977+
# self.assertEqual(math.nextafter(+0.0, -INF), -smallest_subnormal)
1978+
# self.assertEqual(math.nextafter(-0.0, -INF), -smallest_subnormal)
1979+
# self.assertEqualSign(math.nextafter(smallest_subnormal, +0.0), +0.0)
1980+
# self.assertEqualSign(math.nextafter(-smallest_subnormal, +0.0), -0.0)
1981+
# self.assertEqualSign(math.nextafter(smallest_subnormal, -0.0), +0.0)
1982+
# self.assertEqualSign(math.nextafter(-smallest_subnormal, -0.0), -0.0)
1983+
1984+
# around infinity
1985+
largest_normal = sys.float_info.max
1986+
self.assertEqual(math.nextafter(INF, 0.0), largest_normal)
1987+
self.assertEqual(math.nextafter(-INF, 0.0), -largest_normal)
1988+
self.assertEqual(math.nextafter(largest_normal, INF), INF)
1989+
self.assertEqual(math.nextafter(-largest_normal, -INF), -INF)
1990+
1991+
# NaN
1992+
self.assertIsNaN(math.nextafter(NAN, 1.0))
1993+
self.assertIsNaN(math.nextafter(1.0, NAN))
1994+
self.assertIsNaN(math.nextafter(NAN, NAN))
1995+
1996+
@requires_IEEE_754
1997+
def test_ulp(self):
1998+
self.assertEqual(math.ulp(1.0), sys.float_info.epsilon)
1999+
# use int ** int rather than float ** int to not rely on pow() accuracy
2000+
self.assertEqual(math.ulp(2 ** 52), 1.0)
2001+
self.assertEqual(math.ulp(2 ** 53), 2.0)
2002+
self.assertEqual(math.ulp(2 ** 64), 4096.0)
2003+
2004+
# min and max
2005+
# TODO: RUSTPYTHON
2006+
# self.assertEqual(math.ulp(0.0),
2007+
# sys.float_info.min * sys.float_info.epsilon)
2008+
self.assertEqual(math.ulp(FLOAT_MAX),
2009+
FLOAT_MAX - math.nextafter(FLOAT_MAX, -INF))
2010+
2011+
# special cases
2012+
self.assertEqual(math.ulp(INF), INF)
2013+
self.assertIsNaN(math.ulp(math.nan))
2014+
2015+
# negative number: ulp(-x) == ulp(x)
2016+
for x in (0.0, 1.0, 2 ** 52, 2 ** 64, INF):
2017+
with self.subTest(x=x):
2018+
self.assertEqual(math.ulp(-x), math.ulp(x))
20202019

20212020
@unittest.skip('TODO: RUSTPYTHON')
20222021
def test_issue39871(self):
@@ -2183,4 +2182,4 @@ def test_main():
21832182
run_unittest(suite)
21842183

21852184
if __name__ == '__main__':
2186-
test_main()
2185+
test_main()

vm/src/stdlib/math.rs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,21 +363,49 @@ fn math_modf(x: IntoPyFloat) -> (f64, f64) {
363363
(x.fract(), x.trunc())
364364
}
365365

366+
#[inline]
366367
#[cfg(not(target_arch = "wasm32"))]
367-
fn math_nextafter(x: IntoPyFloat, y: IntoPyFloat) -> PyResult<f64> {
368+
fn libc_nextafter(x: f64, y: f64) -> f64 {
368369
extern "C" {
369370
fn nextafter(x: c_double, y: c_double) -> c_double;
370371
}
372+
unsafe { nextafter(x, y) }
373+
}
374+
375+
#[cfg(not(target_arch = "wasm32"))]
376+
fn math_nextafter(x: IntoPyFloat, y: IntoPyFloat) -> PyResult<f64> {
371377
let x = x.to_f64();
372378
let y = y.to_f64();
373-
Ok(unsafe { nextafter(x, y) })
379+
Ok(libc_nextafter(x, y))
374380
}
375381

376382
#[cfg(target_arch = "wasm32")]
377383
fn math_nextafter(_x: IntoPyFloat, _y: IntoPyFloat, vm: &VirtualMachine) -> PyResult<f64> {
378384
Err(vm.new_not_implemented_error("not implemented for this platform".to_owned()))
379385
}
380386

387+
#[cfg(not(target_arch = "wasm32"))]
388+
fn math_ulp(x: IntoPyFloat) -> PyResult<f64> {
389+
let mut x = x.to_f64();
390+
if x.is_nan() {
391+
return Ok(x);
392+
}
393+
x = x.abs();
394+
let mut x2 = libc_nextafter(x, f64::INFINITY);
395+
Ok(if x2.is_infinite() {
396+
// special case: x is the largest positive representable float
397+
x2 = libc_nextafter(x, f64::NEG_INFINITY);
398+
x - x2
399+
} else {
400+
x2 - x
401+
})
402+
}
403+
404+
#[cfg(target_arch = "wasm32")]
405+
fn math_ulp(_x: IntoPyFloat, vm: &VirtualMachine) -> PyResult<f64> {
406+
Err(vm.new_not_implemented_error("not implemented for this platform".to_owned()))
407+
}
408+
381409
fn fmod(x: f64, y: f64) -> f64 {
382410
if y.is_infinite() && x.is_finite() {
383411
return x;
@@ -504,7 +532,9 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
504532
// Factorial function
505533
"factorial" => named_function!(ctx, math, factorial),
506534

535+
// Floating point
507536
"nextafter" => named_function!(ctx, math, nextafter),
537+
"ulp" => named_function!(ctx, math, ulp),
508538

509539
// Constants:
510540
"pi" => ctx.new_float(std::f64::consts::PI), // 3.14159...

0 commit comments

Comments
 (0)