mirror of
https://github.com/python/cpython.git
synced 2025-08-27 20:25:18 +00:00
bpo-29962: add math.remainder (#950)
* Implement math.remainder. * Fix markup for arguments; use double spaces after period. * Mark up function reference in what's new entry. * Add comment explaining the calculation in the final branch. * Fix out-of-order entry in whatsnew. * Add comment explaining why it's good enough to compare m with c, in spite of possible rounding error.
This commit is contained in:
parent
a0157b5f11
commit
a0ce375e10
5 changed files with 268 additions and 0 deletions
|
@ -1000,6 +1000,135 @@ class MathTests(unittest.TestCase):
|
|||
self.ftest('radians(-45)', math.radians(-45), -math.pi/4)
|
||||
self.ftest('radians(0)', math.radians(0), 0)
|
||||
|
||||
@requires_IEEE_754
|
||||
def testRemainder(self):
|
||||
from fractions import Fraction
|
||||
|
||||
def validate_spec(x, y, r):
|
||||
"""
|
||||
Check that r matches remainder(x, y) according to the IEEE 754
|
||||
specification. Assumes that x, y and r are finite and y is nonzero.
|
||||
"""
|
||||
fx, fy, fr = Fraction(x), Fraction(y), Fraction(r)
|
||||
# r should not exceed y/2 in absolute value
|
||||
self.assertLessEqual(abs(fr), abs(fy/2))
|
||||
# x - r should be an exact integer multiple of y
|
||||
n = (fx - fr) / fy
|
||||
self.assertEqual(n, int(n))
|
||||
if abs(fr) == abs(fy/2):
|
||||
# If |r| == |y/2|, n should be even.
|
||||
self.assertEqual(n/2, int(n/2))
|
||||
|
||||
# triples (x, y, remainder(x, y)) in hexadecimal form.
|
||||
testcases = [
|
||||
# Remainders modulo 1, showing the ties-to-even behaviour.
|
||||
'-4.0 1 -0.0',
|
||||
'-3.8 1 0.8',
|
||||
'-3.0 1 -0.0',
|
||||
'-2.8 1 -0.8',
|
||||
'-2.0 1 -0.0',
|
||||
'-1.8 1 0.8',
|
||||
'-1.0 1 -0.0',
|
||||
'-0.8 1 -0.8',
|
||||
'-0.0 1 -0.0',
|
||||
' 0.0 1 0.0',
|
||||
' 0.8 1 0.8',
|
||||
' 1.0 1 0.0',
|
||||
' 1.8 1 -0.8',
|
||||
' 2.0 1 0.0',
|
||||
' 2.8 1 0.8',
|
||||
' 3.0 1 0.0',
|
||||
' 3.8 1 -0.8',
|
||||
' 4.0 1 0.0',
|
||||
|
||||
# Reductions modulo 2*pi
|
||||
'0x0.0p+0 0x1.921fb54442d18p+2 0x0.0p+0',
|
||||
'0x1.921fb54442d18p+0 0x1.921fb54442d18p+2 0x1.921fb54442d18p+0',
|
||||
'0x1.921fb54442d17p+1 0x1.921fb54442d18p+2 0x1.921fb54442d17p+1',
|
||||
'0x1.921fb54442d18p+1 0x1.921fb54442d18p+2 0x1.921fb54442d18p+1',
|
||||
'0x1.921fb54442d19p+1 0x1.921fb54442d18p+2 -0x1.921fb54442d17p+1',
|
||||
'0x1.921fb54442d17p+2 0x1.921fb54442d18p+2 -0x0.0000000000001p+2',
|
||||
'0x1.921fb54442d18p+2 0x1.921fb54442d18p+2 0x0p0',
|
||||
'0x1.921fb54442d19p+2 0x1.921fb54442d18p+2 0x0.0000000000001p+2',
|
||||
'0x1.2d97c7f3321d1p+3 0x1.921fb54442d18p+2 0x1.921fb54442d14p+1',
|
||||
'0x1.2d97c7f3321d2p+3 0x1.921fb54442d18p+2 -0x1.921fb54442d18p+1',
|
||||
'0x1.2d97c7f3321d3p+3 0x1.921fb54442d18p+2 -0x1.921fb54442d14p+1',
|
||||
'0x1.921fb54442d17p+3 0x1.921fb54442d18p+2 -0x0.0000000000001p+3',
|
||||
'0x1.921fb54442d18p+3 0x1.921fb54442d18p+2 0x0p0',
|
||||
'0x1.921fb54442d19p+3 0x1.921fb54442d18p+2 0x0.0000000000001p+3',
|
||||
'0x1.f6a7a2955385dp+3 0x1.921fb54442d18p+2 0x1.921fb54442d14p+1',
|
||||
'0x1.f6a7a2955385ep+3 0x1.921fb54442d18p+2 0x1.921fb54442d18p+1',
|
||||
'0x1.f6a7a2955385fp+3 0x1.921fb54442d18p+2 -0x1.921fb54442d14p+1',
|
||||
'0x1.1475cc9eedf00p+5 0x1.921fb54442d18p+2 0x1.921fb54442d10p+1',
|
||||
'0x1.1475cc9eedf01p+5 0x1.921fb54442d18p+2 -0x1.921fb54442d10p+1',
|
||||
|
||||
# Symmetry with respect to signs.
|
||||
' 1 0.c 0.4',
|
||||
'-1 0.c -0.4',
|
||||
' 1 -0.c 0.4',
|
||||
'-1 -0.c -0.4',
|
||||
' 1.4 0.c -0.4',
|
||||
'-1.4 0.c 0.4',
|
||||
' 1.4 -0.c -0.4',
|
||||
'-1.4 -0.c 0.4',
|
||||
|
||||
# Huge modulus, to check that the underlying algorithm doesn't
|
||||
# rely on 2.0 * modulus being representable.
|
||||
'0x1.dp+1023 0x1.4p+1023 0x0.9p+1023',
|
||||
'0x1.ep+1023 0x1.4p+1023 -0x0.ap+1023',
|
||||
'0x1.fp+1023 0x1.4p+1023 -0x0.9p+1023',
|
||||
]
|
||||
|
||||
for case in testcases:
|
||||
with self.subTest(case=case):
|
||||
x_hex, y_hex, expected_hex = case.split()
|
||||
x = float.fromhex(x_hex)
|
||||
y = float.fromhex(y_hex)
|
||||
expected = float.fromhex(expected_hex)
|
||||
validate_spec(x, y, expected)
|
||||
actual = math.remainder(x, y)
|
||||
# Cheap way of checking that the floats are
|
||||
# as identical as we need them to be.
|
||||
self.assertEqual(actual.hex(), expected.hex())
|
||||
|
||||
# Test tiny subnormal modulus: there's potential for
|
||||
# getting the implementation wrong here (for example,
|
||||
# by assuming that modulus/2 is exactly representable).
|
||||
tiny = float.fromhex('1p-1074') # min +ve subnormal
|
||||
for n in range(-25, 25):
|
||||
if n == 0:
|
||||
continue
|
||||
y = n * tiny
|
||||
for m in range(100):
|
||||
x = m * tiny
|
||||
actual = math.remainder(x, y)
|
||||
validate_spec(x, y, actual)
|
||||
actual = math.remainder(-x, y)
|
||||
validate_spec(-x, y, actual)
|
||||
|
||||
# Special values.
|
||||
# NaNs should propagate as usual.
|
||||
for value in [NAN, 0.0, -0.0, 2.0, -2.3, NINF, INF]:
|
||||
self.assertIsNaN(math.remainder(NAN, value))
|
||||
self.assertIsNaN(math.remainder(value, NAN))
|
||||
|
||||
# remainder(x, inf) is x, for non-nan non-infinite x.
|
||||
for value in [-2.3, -0.0, 0.0, 2.3]:
|
||||
self.assertEqual(math.remainder(value, INF), value)
|
||||
self.assertEqual(math.remainder(value, NINF), value)
|
||||
|
||||
# remainder(x, 0) and remainder(infinity, x) for non-NaN x are invalid
|
||||
# operations according to IEEE 754-2008 7.2(f), and should raise.
|
||||
for value in [NINF, -2.3, -0.0, 0.0, 2.3, INF]:
|
||||
with self.assertRaises(ValueError):
|
||||
math.remainder(INF, value)
|
||||
with self.assertRaises(ValueError):
|
||||
math.remainder(NINF, value)
|
||||
with self.assertRaises(ValueError):
|
||||
math.remainder(value, 0.0)
|
||||
with self.assertRaises(ValueError):
|
||||
math.remainder(value, -0.0)
|
||||
|
||||
def testSin(self):
|
||||
self.assertRaises(TypeError, math.sin)
|
||||
self.ftest('sin(0)', math.sin(0), 0)
|
||||
|
@ -1286,6 +1415,12 @@ class MathTests(unittest.TestCase):
|
|||
self.fail('Failures in test_mtestfile:\n ' +
|
||||
'\n '.join(failures))
|
||||
|
||||
# Custom assertions.
|
||||
|
||||
def assertIsNaN(self, value):
|
||||
if not math.isnan(value):
|
||||
self.fail("Expected a NaN, got {!r}.".format(value))
|
||||
|
||||
|
||||
class IsCloseTests(unittest.TestCase):
|
||||
isclose = math.isclose # sublcasses should override this
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue