[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