Coverage for /usr/lib/python3/dist-packages/sympy/core/mod.py: 10%
155 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1from .add import Add
2from .exprtools import gcd_terms
3from .function import Function
4from .kind import NumberKind
5from .logic import fuzzy_and, fuzzy_not
6from .mul import Mul
7from .numbers import equal_valued
8from .singleton import S
11class Mod(Function):
12 """Represents a modulo operation on symbolic expressions.
14 Parameters
15 ==========
17 p : Expr
18 Dividend.
20 q : Expr
21 Divisor.
23 Notes
24 =====
26 The convention used is the same as Python's: the remainder always has the
27 same sign as the divisor.
29 Examples
30 ========
32 >>> from sympy.abc import x, y
33 >>> x**2 % y
34 Mod(x**2, y)
35 >>> _.subs({x: 5, y: 6})
36 1
38 """
40 kind = NumberKind
42 @classmethod
43 def eval(cls, p, q):
44 def number_eval(p, q):
45 """Try to return p % q if both are numbers or +/-p is known
46 to be less than or equal q.
47 """
49 if q.is_zero:
50 raise ZeroDivisionError("Modulo by zero")
51 if p is S.NaN or q is S.NaN or p.is_finite is False or q.is_finite is False:
52 return S.NaN
53 if p is S.Zero or p in (q, -q) or (p.is_integer and q == 1):
54 return S.Zero
56 if q.is_Number:
57 if p.is_Number:
58 return p%q
59 if q == 2:
60 if p.is_even:
61 return S.Zero
62 elif p.is_odd:
63 return S.One
65 if hasattr(p, '_eval_Mod'):
66 rv = getattr(p, '_eval_Mod')(q)
67 if rv is not None:
68 return rv
70 # by ratio
71 r = p/q
72 if r.is_integer:
73 return S.Zero
74 try:
75 d = int(r)
76 except TypeError:
77 pass
78 else:
79 if isinstance(d, int):
80 rv = p - d*q
81 if (rv*q < 0) == True:
82 rv += q
83 return rv
85 # by difference
86 # -2|q| < p < 2|q|
87 d = abs(p)
88 for _ in range(2):
89 d -= abs(q)
90 if d.is_negative:
91 if q.is_positive:
92 if p.is_positive:
93 return d + q
94 elif p.is_negative:
95 return -d
96 elif q.is_negative:
97 if p.is_positive:
98 return d
99 elif p.is_negative:
100 return -d + q
101 break
103 rv = number_eval(p, q)
104 if rv is not None:
105 return rv
107 # denest
108 if isinstance(p, cls):
109 qinner = p.args[1]
110 if qinner % q == 0:
111 return cls(p.args[0], q)
112 elif (qinner*(q - qinner)).is_nonnegative:
113 # |qinner| < |q| and have same sign
114 return p
115 elif isinstance(-p, cls):
116 qinner = (-p).args[1]
117 if qinner % q == 0:
118 return cls(-(-p).args[0], q)
119 elif (qinner*(q + qinner)).is_nonpositive:
120 # |qinner| < |q| and have different sign
121 return p
122 elif isinstance(p, Add):
123 # separating into modulus and non modulus
124 both_l = non_mod_l, mod_l = [], []
125 for arg in p.args:
126 both_l[isinstance(arg, cls)].append(arg)
127 # if q same for all
128 if mod_l and all(inner.args[1] == q for inner in mod_l):
129 net = Add(*non_mod_l) + Add(*[i.args[0] for i in mod_l])
130 return cls(net, q)
132 elif isinstance(p, Mul):
133 # separating into modulus and non modulus
134 both_l = non_mod_l, mod_l = [], []
135 for arg in p.args:
136 both_l[isinstance(arg, cls)].append(arg)
138 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:
139 # finding distributive term
140 non_mod_l = [cls(x, q) for x in non_mod_l]
141 mod = []
142 non_mod = []
143 for j in non_mod_l:
144 if isinstance(j, cls):
145 mod.append(j.args[0])
146 else:
147 non_mod.append(j)
148 prod_mod = Mul(*mod)
149 prod_non_mod = Mul(*non_mod)
150 prod_mod1 = Mul(*[i.args[0] for i in mod_l])
151 net = prod_mod1*prod_mod
152 return prod_non_mod*cls(net, q)
154 if q.is_Integer and q is not S.One:
155 non_mod_l = [i % q if i.is_Integer and (i % q is not S.Zero) else i for
156 i in non_mod_l]
158 p = Mul(*(non_mod_l + mod_l))
160 # XXX other possibilities?
162 from sympy.polys.polyerrors import PolynomialError
163 from sympy.polys.polytools import gcd
165 # extract gcd; any further simplification should be done by the user
166 try:
167 G = gcd(p, q)
168 if not equal_valued(G, 1):
169 p, q = [gcd_terms(i/G, clear=False, fraction=False)
170 for i in (p, q)]
171 except PolynomialError: # issue 21373
172 G = S.One
173 pwas, qwas = p, q
175 # simplify terms
176 # (x + y + 2) % x -> Mod(y + 2, x)
177 if p.is_Add:
178 args = []
179 for i in p.args:
180 a = cls(i, q)
181 if a.count(cls) > i.count(cls):
182 args.append(i)
183 else:
184 args.append(a)
185 if args != list(p.args):
186 p = Add(*args)
188 else:
189 # handle coefficients if they are not Rational
190 # since those are not handled by factor_terms
191 # e.g. Mod(.6*x, .3*y) -> 0.3*Mod(2*x, y)
192 cp, p = p.as_coeff_Mul()
193 cq, q = q.as_coeff_Mul()
194 ok = False
195 if not cp.is_Rational or not cq.is_Rational:
196 r = cp % cq
197 if equal_valued(r, 0):
198 G *= cq
199 p *= int(cp/cq)
200 ok = True
201 if not ok:
202 p = cp*p
203 q = cq*q
205 # simple -1 extraction
206 if p.could_extract_minus_sign() and q.could_extract_minus_sign():
207 G, p, q = [-i for i in (G, p, q)]
209 # check again to see if p and q can now be handled as numbers
210 rv = number_eval(p, q)
211 if rv is not None:
212 return rv*G
214 # put 1.0 from G on inside
215 if G.is_Float and equal_valued(G, 1):
216 p *= G
217 return cls(p, q, evaluate=False)
218 elif G.is_Mul and G.args[0].is_Float and equal_valued(G.args[0], 1):
219 p = G.args[0]*p
220 G = Mul._from_args(G.args[1:])
221 return G*cls(p, q, evaluate=(p, q) != (pwas, qwas))
223 def _eval_is_integer(self):
224 p, q = self.args
225 if fuzzy_and([p.is_integer, q.is_integer, fuzzy_not(q.is_zero)]):
226 return True
228 def _eval_is_nonnegative(self):
229 if self.args[1].is_positive:
230 return True
232 def _eval_is_nonpositive(self):
233 if self.args[1].is_negative:
234 return True
236 def _eval_rewrite_as_floor(self, a, b, **kwargs):
237 from sympy.functions.elementary.integers import floor
238 return a - b*floor(a/b)