[pypy-commit] pypy py3.7: Merged in py3.7-bpo-29962 (pull request #681)

arigo pypy.commits at gmail.com
Thu Nov 7 07:36:41 EST 2019


Author: Armin Rigo <armin.rigo at gmail.com>
Branch: py3.7
Changeset: r97975:63e99c51c669
Date: 2019-11-07 12:36 +0000
http://bitbucket.org/pypy/pypy/changeset/63e99c51c669/

Log:	Merged in py3.7-bpo-29962 (pull request #681)

	bpo-29962: Implemented app-level math.remainder

diff --git a/pypy/module/math/app_math.py b/pypy/module/math/app_math.py
--- a/pypy/module/math/app_math.py
+++ b/pypy/module/math/app_math.py
@@ -47,3 +47,85 @@
     res, _, shift = _fac1(x)
     return res << shift
 
+
+def remainder(x, y):
+    """Difference between x and the closest integer multiple of y.
+
+    Return x - n*y where n*y is the closest integer multiple of y.
+    In the case where x is exactly halfway between two multiples of
+    y, the nearest even value of n is used. The result is always exact."""
+
+    from math import copysign, fabs, fmod, isfinite, isinf, isnan, nan
+
+    x = float(x)
+    y = float(y)
+
+    # Deal with most common case first.
+    if isfinite(x) and isfinite(y):
+        if y == 0.0:
+            # return nan
+            # Merging the logic from math_2 in CPython's mathmodule.c
+            # nan returned and x and y both not nan -> domain error
+            raise ValueError("math domain error")
+        
+        absx = fabs(x)
+        absy = fabs(y)
+        m = fmod(absx, absy)
+
+        # Warning: some subtlety here. What we *want* to know at this point is
+        # whether the remainder m is less than, equal to, or greater than half
+        # of absy. However, we can't do that comparison directly because we
+        # can't be sure that 0.5*absy is representable (the mutiplication
+        # might incur precision loss due to underflow). So instead we compare
+        # m with the complement c = absy - m: m < 0.5*absy if and only if m <
+        # c, and so on. The catch is that absy - m might also not be
+        # representable, but it turns out that it doesn't matter:
+        # - if m > 0.5*absy then absy - m is exactly representable, by
+        #     Sterbenz's lemma, so m > c
+        # - if m == 0.5*absy then again absy - m is exactly representable
+        #     and m == c
+        # - if m < 0.5*absy then either (i) 0.5*absy is exactly representable,
+        #     in which case 0.5*absy < absy - m, so 0.5*absy <= c and hence m <
+        #     c, or (ii) absy is tiny, either subnormal or in the lowest normal
+        #     binade. Then absy - m is exactly representable and again m < c.
+
+        c = absy - m
+        if m < c:
+            r = m
+        elif m > c:
+            r = -c
+        else:
+            # Here absx is exactly halfway between two multiples of absy,
+            # and we need to choose the even multiple. x now has the form
+            #     absx = n * absy + m
+            # for some integer n (recalling that m = 0.5*absy at this point).
+            # If n is even we want to return m; if n is odd, we need to
+            # return -m.
+            # So
+            #     0.5 * (absx - m) = (n/2) * absy
+            # and now reducing modulo absy gives us:
+            #                                     | m, if n is odd
+            #     fmod(0.5 * (absx - m), absy) = |
+            #                                     | 0, if n is even
+            # Now m - 2.0 * fmod(...) gives the desired result: m
+            # if n is even, -m if m is odd.
+            # Note that all steps in fmod(0.5 * (absx - m), absy)
+            # will be computed exactly, with no rounding error
+            # introduced.
+            assert m == c
+            r = m - 2.0 * fmod(0.5 * (absx - m), absy)
+        return copysign(1.0, x) * r
+    
+    # Special values.
+    if isnan(x):
+        return x
+    if isnan(y):
+        return y
+    if isinf(x):
+        # return nan
+        # Merging the logic from math_2 in CPython's mathmodule.c
+        # nan returned and x and y both not nan -> domain error
+        raise ValueError("math domain error")
+    assert isinf(y)
+    return x
+ 
\ No newline at end of file
diff --git a/pypy/module/math/moduledef.py b/pypy/module/math/moduledef.py
--- a/pypy/module/math/moduledef.py
+++ b/pypy/module/math/moduledef.py
@@ -5,6 +5,7 @@
 class Module(MixedModule):
     appleveldefs = {
        'factorial' : 'app_math.factorial',
+       'remainder' : 'app_math.remainder',
     }
 
     interpleveldefs = {
diff --git a/pypy/module/math/test/test_math.py b/pypy/module/math/test/test_math.py
--- a/pypy/module/math/test/test_math.py
+++ b/pypy/module/math/test/test_math.py
@@ -386,3 +386,17 @@
     def test_pi_tau(self):
         import math
         assert math.tau == math.pi * 2.0
+
+    def test_remainder(self):
+        import math
+        assert math.remainder(3, math.pi) == 3 - math.pi
+        assert math.remainder(-3, math.pi) == math.pi - 3
+        assert math.remainder(3, -math.pi) == 3 - math.pi
+        assert math.remainder(4, math.pi) == 4 - math.pi
+        assert math.remainder(6, math.pi) == 6 - 2 * math.pi
+        assert math.remainder(3, math.inf) == 3
+        assert math.remainder(3, -math.inf) == 3
+        assert math.isnan(math.remainder(3, math.nan))
+        assert math.isnan(math.remainder(math.nan, 3))
+        raises(ValueError, math.remainder, 3, 0)
+        raises(ValueError, math.remainder, math.inf, 3)


More information about the pypy-commit mailing list