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

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 

9 

10 

11class Mod(Function): 

12 """Represents a modulo operation on symbolic expressions. 

13 

14 Parameters 

15 ========== 

16 

17 p : Expr 

18 Dividend. 

19 

20 q : Expr 

21 Divisor. 

22 

23 Notes 

24 ===== 

25 

26 The convention used is the same as Python's: the remainder always has the 

27 same sign as the divisor. 

28 

29 Examples 

30 ======== 

31 

32 >>> from sympy.abc import x, y 

33 >>> x**2 % y 

34 Mod(x**2, y) 

35 >>> _.subs({x: 5, y: 6}) 

36 1 

37 

38 """ 

39 

40 kind = NumberKind 

41 

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 """ 

48 

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 

55 

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 

64 

65 if hasattr(p, '_eval_Mod'): 

66 rv = getattr(p, '_eval_Mod')(q) 

67 if rv is not None: 

68 return rv 

69 

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 

84 

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 

102 

103 rv = number_eval(p, q) 

104 if rv is not None: 

105 return rv 

106 

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) 

131 

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) 

137 

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) 

153 

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] 

157 

158 p = Mul(*(non_mod_l + mod_l)) 

159 

160 # XXX other possibilities? 

161 

162 from sympy.polys.polyerrors import PolynomialError 

163 from sympy.polys.polytools import gcd 

164 

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 

174 

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) 

187 

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 

204 

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)] 

208 

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 

213 

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)) 

222 

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 

227 

228 def _eval_is_nonnegative(self): 

229 if self.args[1].is_positive: 

230 return True 

231 

232 def _eval_is_nonpositive(self): 

233 if self.args[1].is_negative: 

234 return True 

235 

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)