summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorMark Dickinson <mdickinson@enthought.com>2017-04-05 17:34:27 (GMT)
committerGitHub <noreply@github.com>2017-04-05 17:34:27 (GMT)
commita0ce375e10b50f7606cb86b072fed7d8cd574fe7 (patch)
tree27237508c442d340385a2dcf0a64278ca894008c /Lib
parenta0157b5f11e621f2196af4e918b9f07688a6cd1c (diff)
downloadcpython-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')
-rw-r--r--Lib/test/test_math.py135
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