123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- from .add import Add
- from .exprtools import gcd_terms
- from .function import Function
- from .kind import NumberKind
- from .logic import fuzzy_and, fuzzy_not
- from .mul import Mul
- from .numbers import equal_valued
- from .singleton import S
- class Mod(Function):
- """Represents a modulo operation on symbolic expressions.
- Parameters
- ==========
- p : Expr
- Dividend.
- q : Expr
- Divisor.
- Notes
- =====
- The convention used is the same as Python's: the remainder always has the
- same sign as the divisor.
- Examples
- ========
- >>> from sympy.abc import x, y
- >>> x**2 % y
- Mod(x**2, y)
- >>> _.subs({x: 5, y: 6})
- 1
- """
- kind = NumberKind
- @classmethod
- def eval(cls, p, q):
- def number_eval(p, q):
- """Try to return p % q if both are numbers or +/-p is known
- to be less than or equal q.
- """
- if q.is_zero:
- raise ZeroDivisionError("Modulo by zero")
- if p is S.NaN or q is S.NaN or p.is_finite is False or q.is_finite is False:
- return S.NaN
- if p is S.Zero or p in (q, -q) or (p.is_integer and q == 1):
- return S.Zero
- if q.is_Number:
- if p.is_Number:
- return p%q
- if q == 2:
- if p.is_even:
- return S.Zero
- elif p.is_odd:
- return S.One
- if hasattr(p, '_eval_Mod'):
- rv = getattr(p, '_eval_Mod')(q)
- if rv is not None:
- return rv
- # by ratio
- r = p/q
- if r.is_integer:
- return S.Zero
- try:
- d = int(r)
- except TypeError:
- pass
- else:
- if isinstance(d, int):
- rv = p - d*q
- if (rv*q < 0) == True:
- rv += q
- return rv
- # by difference
- # -2|q| < p < 2|q|
- d = abs(p)
- for _ in range(2):
- d -= abs(q)
- if d.is_negative:
- if q.is_positive:
- if p.is_positive:
- return d + q
- elif p.is_negative:
- return -d
- elif q.is_negative:
- if p.is_positive:
- return d
- elif p.is_negative:
- return -d + q
- break
- rv = number_eval(p, q)
- if rv is not None:
- return rv
- # denest
- if isinstance(p, cls):
- qinner = p.args[1]
- if qinner % q == 0:
- return cls(p.args[0], q)
- elif (qinner*(q - qinner)).is_nonnegative:
- # |qinner| < |q| and have same sign
- return p
- elif isinstance(-p, cls):
- qinner = (-p).args[1]
- if qinner % q == 0:
- return cls(-(-p).args[0], q)
- elif (qinner*(q + qinner)).is_nonpositive:
- # |qinner| < |q| and have different sign
- return p
- elif isinstance(p, Add):
- # separating into modulus and non modulus
- both_l = non_mod_l, mod_l = [], []
- for arg in p.args:
- both_l[isinstance(arg, cls)].append(arg)
- # if q same for all
- if mod_l and all(inner.args[1] == q for inner in mod_l):
- net = Add(*non_mod_l) + Add(*[i.args[0] for i in mod_l])
- return cls(net, q)
- elif isinstance(p, Mul):
- # separating into modulus and non modulus
- both_l = non_mod_l, mod_l = [], []
- for arg in p.args:
- both_l[isinstance(arg, cls)].append(arg)
- if mod_l and all(inner.args[1] == q for inner in mod_l) and all(t.is_integer for t in p.args) and q.is_integer:
- # finding distributive term
- non_mod_l = [cls(x, q) for x in non_mod_l]
- mod = []
- non_mod = []
- for j in non_mod_l:
- if isinstance(j, cls):
- mod.append(j.args[0])
- else:
- non_mod.append(j)
- prod_mod = Mul(*mod)
- prod_non_mod = Mul(*non_mod)
- prod_mod1 = Mul(*[i.args[0] for i in mod_l])
- net = prod_mod1*prod_mod
- return prod_non_mod*cls(net, q)
- if q.is_Integer and q is not S.One:
- non_mod_l = [i % q if i.is_Integer and (i % q is not S.Zero) else i for
- i in non_mod_l]
- p = Mul(*(non_mod_l + mod_l))
- # XXX other possibilities?
- from sympy.polys.polyerrors import PolynomialError
- from sympy.polys.polytools import gcd
- # extract gcd; any further simplification should be done by the user
- try:
- G = gcd(p, q)
- if not equal_valued(G, 1):
- p, q = [gcd_terms(i/G, clear=False, fraction=False)
- for i in (p, q)]
- except PolynomialError: # issue 21373
- G = S.One
- pwas, qwas = p, q
- # simplify terms
- # (x + y + 2) % x -> Mod(y + 2, x)
- if p.is_Add:
- args = []
- for i in p.args:
- a = cls(i, q)
- if a.count(cls) > i.count(cls):
- args.append(i)
- else:
- args.append(a)
- if args != list(p.args):
- p = Add(*args)
- else:
- # handle coefficients if they are not Rational
- # since those are not handled by factor_terms
- # e.g. Mod(.6*x, .3*y) -> 0.3*Mod(2*x, y)
- cp, p = p.as_coeff_Mul()
- cq, q = q.as_coeff_Mul()
- ok = False
- if not cp.is_Rational or not cq.is_Rational:
- r = cp % cq
- if equal_valued(r, 0):
- G *= cq
- p *= int(cp/cq)
- ok = True
- if not ok:
- p = cp*p
- q = cq*q
- # simple -1 extraction
- if p.could_extract_minus_sign() and q.could_extract_minus_sign():
- G, p, q = [-i for i in (G, p, q)]
- # check again to see if p and q can now be handled as numbers
- rv = number_eval(p, q)
- if rv is not None:
- return rv*G
- # put 1.0 from G on inside
- if G.is_Float and equal_valued(G, 1):
- p *= G
- return cls(p, q, evaluate=False)
- elif G.is_Mul and G.args[0].is_Float and equal_valued(G.args[0], 1):
- p = G.args[0]*p
- G = Mul._from_args(G.args[1:])
- return G*cls(p, q, evaluate=(p, q) != (pwas, qwas))
- def _eval_is_integer(self):
- p, q = self.args
- if fuzzy_and([p.is_integer, q.is_integer, fuzzy_not(q.is_zero)]):
- return True
- def _eval_is_nonnegative(self):
- if self.args[1].is_positive:
- return True
- def _eval_is_nonpositive(self):
- if self.args[1].is_negative:
- return True
- def _eval_rewrite_as_floor(self, a, b, **kwargs):
- from sympy.functions.elementary.integers import floor
- return a - b*floor(a/b)
|