diff options
author | Mark Dickinson <mdickinson@enthought.com> | 2017-04-05 17:34:27 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-04-05 17:34:27 (GMT) |
commit | a0ce375e10b50f7606cb86b072fed7d8cd574fe7 (patch) | |
tree | 27237508c442d340385a2dcf0a64278ca894008c /Lib/test | |
parent | a0157b5f11e621f2196af4e918b9f07688a6cd1c (diff) | |
download | cpython-a0ce375e10b50f7606cb86b072fed7d8cd574fe7.zip cpython-a0ce375e10b50f7606cb86b072fed7d8cd574fe7.tar.gz cpython-a0ce375e10b50f7606cb86b072fed7d8cd574fe7.tar.bz2 |
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.
Diffstat (limited to 'Lib/test')
-rw-r--r-- | Lib/test/test_math.py | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/Lib/test/test_math.py b/Lib/test/test_math.py index eaa41bc..70cb574 100644 --- a/Lib/test/test_math.py +++ b/Lib/test/test_math.py @@ -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 |