Coverage for /usr/lib/python3/dist-packages/sympy/functions/elementary/integers.py: 20%
388 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 typing import Tuple as tTuple
3from sympy.core.basic import Basic
4from sympy.core.expr import Expr
6from sympy.core import Add, S
7from sympy.core.evalf import get_integer_part, PrecisionExhausted
8from sympy.core.function import Function
9from sympy.core.logic import fuzzy_or
10from sympy.core.numbers import Integer
11from sympy.core.relational import Gt, Lt, Ge, Le, Relational, is_eq
12from sympy.core.symbol import Symbol
13from sympy.core.sympify import _sympify
14from sympy.functions.elementary.complexes import im, re
15from sympy.multipledispatch import dispatch
17###############################################################################
18######################### FLOOR and CEILING FUNCTIONS #########################
19###############################################################################
22class RoundFunction(Function):
23 """Abstract base class for rounding functions."""
25 args: tTuple[Expr]
27 @classmethod
28 def eval(cls, arg):
29 v = cls._eval_number(arg)
30 if v is not None:
31 return v
33 if arg.is_integer or arg.is_finite is False:
34 return arg
35 if arg.is_imaginary or (S.ImaginaryUnit*arg).is_real:
36 i = im(arg)
37 if not i.has(S.ImaginaryUnit):
38 return cls(i)*S.ImaginaryUnit
39 return cls(arg, evaluate=False)
41 # Integral, numerical, symbolic part
42 ipart = npart = spart = S.Zero
44 # Extract integral (or complex integral) terms
45 terms = Add.make_args(arg)
47 for t in terms:
48 if t.is_integer or (t.is_imaginary and im(t).is_integer):
49 ipart += t
50 elif t.has(Symbol):
51 spart += t
52 else:
53 npart += t
55 if not (npart or spart):
56 return ipart
58 # Evaluate npart numerically if independent of spart
59 if npart and (
60 not spart or
61 npart.is_real and (spart.is_imaginary or (S.ImaginaryUnit*spart).is_real) or
62 npart.is_imaginary and spart.is_real):
63 try:
64 r, i = get_integer_part(
65 npart, cls._dir, {}, return_ints=True)
66 ipart += Integer(r) + Integer(i)*S.ImaginaryUnit
67 npart = S.Zero
68 except (PrecisionExhausted, NotImplementedError):
69 pass
71 spart += npart
72 if not spart:
73 return ipart
74 elif spart.is_imaginary or (S.ImaginaryUnit*spart).is_real:
75 return ipart + cls(im(spart), evaluate=False)*S.ImaginaryUnit
76 elif isinstance(spart, (floor, ceiling)):
77 return ipart + spart
78 else:
79 return ipart + cls(spart, evaluate=False)
81 @classmethod
82 def _eval_number(cls, arg):
83 raise NotImplementedError()
85 def _eval_is_finite(self):
86 return self.args[0].is_finite
88 def _eval_is_real(self):
89 return self.args[0].is_real
91 def _eval_is_integer(self):
92 return self.args[0].is_real
95class floor(RoundFunction):
96 """
97 Floor is a univariate function which returns the largest integer
98 value not greater than its argument. This implementation
99 generalizes floor to complex numbers by taking the floor of the
100 real and imaginary parts separately.
102 Examples
103 ========
105 >>> from sympy import floor, E, I, S, Float, Rational
106 >>> floor(17)
107 17
108 >>> floor(Rational(23, 10))
109 2
110 >>> floor(2*E)
111 5
112 >>> floor(-Float(0.567))
113 -1
114 >>> floor(-I/2)
115 -I
116 >>> floor(S(5)/2 + 5*I/2)
117 2 + 2*I
119 See Also
120 ========
122 sympy.functions.elementary.integers.ceiling
124 References
125 ==========
127 .. [1] "Concrete mathematics" by Graham, pp. 87
128 .. [2] https://mathworld.wolfram.com/FloorFunction.html
130 """
131 _dir = -1
133 @classmethod
134 def _eval_number(cls, arg):
135 if arg.is_Number:
136 return arg.floor()
137 elif any(isinstance(i, j)
138 for i in (arg, -arg) for j in (floor, ceiling)):
139 return arg
140 if arg.is_NumberSymbol:
141 return arg.approximation_interval(Integer)[0]
143 def _eval_as_leading_term(self, x, logx=None, cdir=0):
144 from sympy.calculus.accumulationbounds import AccumBounds
145 arg = self.args[0]
146 arg0 = arg.subs(x, 0)
147 r = self.subs(x, 0)
148 if arg0 is S.NaN or isinstance(arg0, AccumBounds):
149 arg0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+')
150 r = floor(arg0)
151 if arg0.is_finite:
152 if arg0 == r:
153 ndir = arg.dir(x, cdir=cdir)
154 return r - 1 if ndir.is_negative else r
155 else:
156 return r
157 return arg.as_leading_term(x, logx=logx, cdir=cdir)
159 def _eval_nseries(self, x, n, logx, cdir=0):
160 arg = self.args[0]
161 arg0 = arg.subs(x, 0)
162 r = self.subs(x, 0)
163 if arg0 is S.NaN:
164 arg0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+')
165 r = floor(arg0)
166 if arg0.is_infinite:
167 from sympy.calculus.accumulationbounds import AccumBounds
168 from sympy.series.order import Order
169 s = arg._eval_nseries(x, n, logx, cdir)
170 o = Order(1, (x, 0)) if n <= 0 else AccumBounds(-1, 0)
171 return s + o
172 if arg0 == r:
173 ndir = arg.dir(x, cdir=cdir if cdir != 0 else 1)
174 return r - 1 if ndir.is_negative else r
175 else:
176 return r
178 def _eval_is_negative(self):
179 return self.args[0].is_negative
181 def _eval_is_nonnegative(self):
182 return self.args[0].is_nonnegative
184 def _eval_rewrite_as_ceiling(self, arg, **kwargs):
185 return -ceiling(-arg)
187 def _eval_rewrite_as_frac(self, arg, **kwargs):
188 return arg - frac(arg)
190 def __le__(self, other):
191 other = S(other)
192 if self.args[0].is_real:
193 if other.is_integer:
194 return self.args[0] < other + 1
195 if other.is_number and other.is_real:
196 return self.args[0] < ceiling(other)
197 if self.args[0] == other and other.is_real:
198 return S.true
199 if other is S.Infinity and self.is_finite:
200 return S.true
202 return Le(self, other, evaluate=False)
204 def __ge__(self, other):
205 other = S(other)
206 if self.args[0].is_real:
207 if other.is_integer:
208 return self.args[0] >= other
209 if other.is_number and other.is_real:
210 return self.args[0] >= ceiling(other)
211 if self.args[0] == other and other.is_real:
212 return S.false
213 if other is S.NegativeInfinity and self.is_finite:
214 return S.true
216 return Ge(self, other, evaluate=False)
218 def __gt__(self, other):
219 other = S(other)
220 if self.args[0].is_real:
221 if other.is_integer:
222 return self.args[0] >= other + 1
223 if other.is_number and other.is_real:
224 return self.args[0] >= ceiling(other)
225 if self.args[0] == other and other.is_real:
226 return S.false
227 if other is S.NegativeInfinity and self.is_finite:
228 return S.true
230 return Gt(self, other, evaluate=False)
232 def __lt__(self, other):
233 other = S(other)
234 if self.args[0].is_real:
235 if other.is_integer:
236 return self.args[0] < other
237 if other.is_number and other.is_real:
238 return self.args[0] < ceiling(other)
239 if self.args[0] == other and other.is_real:
240 return S.false
241 if other is S.Infinity and self.is_finite:
242 return S.true
244 return Lt(self, other, evaluate=False)
247@dispatch(floor, Expr)
248def _eval_is_eq(lhs, rhs): # noqa:F811
249 return is_eq(lhs.rewrite(ceiling), rhs) or \
250 is_eq(lhs.rewrite(frac),rhs)
253class ceiling(RoundFunction):
254 """
255 Ceiling is a univariate function which returns the smallest integer
256 value not less than its argument. This implementation
257 generalizes ceiling to complex numbers by taking the ceiling of the
258 real and imaginary parts separately.
260 Examples
261 ========
263 >>> from sympy import ceiling, E, I, S, Float, Rational
264 >>> ceiling(17)
265 17
266 >>> ceiling(Rational(23, 10))
267 3
268 >>> ceiling(2*E)
269 6
270 >>> ceiling(-Float(0.567))
271 0
272 >>> ceiling(I/2)
273 I
274 >>> ceiling(S(5)/2 + 5*I/2)
275 3 + 3*I
277 See Also
278 ========
280 sympy.functions.elementary.integers.floor
282 References
283 ==========
285 .. [1] "Concrete mathematics" by Graham, pp. 87
286 .. [2] https://mathworld.wolfram.com/CeilingFunction.html
288 """
289 _dir = 1
291 @classmethod
292 def _eval_number(cls, arg):
293 if arg.is_Number:
294 return arg.ceiling()
295 elif any(isinstance(i, j)
296 for i in (arg, -arg) for j in (floor, ceiling)):
297 return arg
298 if arg.is_NumberSymbol:
299 return arg.approximation_interval(Integer)[1]
301 def _eval_as_leading_term(self, x, logx=None, cdir=0):
302 from sympy.calculus.accumulationbounds import AccumBounds
303 arg = self.args[0]
304 arg0 = arg.subs(x, 0)
305 r = self.subs(x, 0)
306 if arg0 is S.NaN or isinstance(arg0, AccumBounds):
307 arg0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+')
308 r = ceiling(arg0)
309 if arg0.is_finite:
310 if arg0 == r:
311 ndir = arg.dir(x, cdir=cdir)
312 return r if ndir.is_negative else r + 1
313 else:
314 return r
315 return arg.as_leading_term(x, logx=logx, cdir=cdir)
317 def _eval_nseries(self, x, n, logx, cdir=0):
318 arg = self.args[0]
319 arg0 = arg.subs(x, 0)
320 r = self.subs(x, 0)
321 if arg0 is S.NaN:
322 arg0 = arg.limit(x, 0, dir='-' if re(cdir).is_negative else '+')
323 r = ceiling(arg0)
324 if arg0.is_infinite:
325 from sympy.calculus.accumulationbounds import AccumBounds
326 from sympy.series.order import Order
327 s = arg._eval_nseries(x, n, logx, cdir)
328 o = Order(1, (x, 0)) if n <= 0 else AccumBounds(0, 1)
329 return s + o
330 if arg0 == r:
331 ndir = arg.dir(x, cdir=cdir if cdir != 0 else 1)
332 return r if ndir.is_negative else r + 1
333 else:
334 return r
336 def _eval_rewrite_as_floor(self, arg, **kwargs):
337 return -floor(-arg)
339 def _eval_rewrite_as_frac(self, arg, **kwargs):
340 return arg + frac(-arg)
342 def _eval_is_positive(self):
343 return self.args[0].is_positive
345 def _eval_is_nonpositive(self):
346 return self.args[0].is_nonpositive
348 def __lt__(self, other):
349 other = S(other)
350 if self.args[0].is_real:
351 if other.is_integer:
352 return self.args[0] <= other - 1
353 if other.is_number and other.is_real:
354 return self.args[0] <= floor(other)
355 if self.args[0] == other and other.is_real:
356 return S.false
357 if other is S.Infinity and self.is_finite:
358 return S.true
360 return Lt(self, other, evaluate=False)
362 def __gt__(self, other):
363 other = S(other)
364 if self.args[0].is_real:
365 if other.is_integer:
366 return self.args[0] > other
367 if other.is_number and other.is_real:
368 return self.args[0] > floor(other)
369 if self.args[0] == other and other.is_real:
370 return S.false
371 if other is S.NegativeInfinity and self.is_finite:
372 return S.true
374 return Gt(self, other, evaluate=False)
376 def __ge__(self, other):
377 other = S(other)
378 if self.args[0].is_real:
379 if other.is_integer:
380 return self.args[0] > other - 1
381 if other.is_number and other.is_real:
382 return self.args[0] > floor(other)
383 if self.args[0] == other and other.is_real:
384 return S.true
385 if other is S.NegativeInfinity and self.is_finite:
386 return S.true
388 return Ge(self, other, evaluate=False)
390 def __le__(self, other):
391 other = S(other)
392 if self.args[0].is_real:
393 if other.is_integer:
394 return self.args[0] <= other
395 if other.is_number and other.is_real:
396 return self.args[0] <= floor(other)
397 if self.args[0] == other and other.is_real:
398 return S.false
399 if other is S.Infinity and self.is_finite:
400 return S.true
402 return Le(self, other, evaluate=False)
405@dispatch(ceiling, Basic) # type:ignore
406def _eval_is_eq(lhs, rhs): # noqa:F811
407 return is_eq(lhs.rewrite(floor), rhs) or is_eq(lhs.rewrite(frac),rhs)
410class frac(Function):
411 r"""Represents the fractional part of x
413 For real numbers it is defined [1]_ as
415 .. math::
416 x - \left\lfloor{x}\right\rfloor
418 Examples
419 ========
421 >>> from sympy import Symbol, frac, Rational, floor, I
422 >>> frac(Rational(4, 3))
423 1/3
424 >>> frac(-Rational(4, 3))
425 2/3
427 returns zero for integer arguments
429 >>> n = Symbol('n', integer=True)
430 >>> frac(n)
431 0
433 rewrite as floor
435 >>> x = Symbol('x')
436 >>> frac(x).rewrite(floor)
437 x - floor(x)
439 for complex arguments
441 >>> r = Symbol('r', real=True)
442 >>> t = Symbol('t', real=True)
443 >>> frac(t + I*r)
444 I*frac(r) + frac(t)
446 See Also
447 ========
449 sympy.functions.elementary.integers.floor
450 sympy.functions.elementary.integers.ceiling
452 References
453 ===========
455 .. [1] https://en.wikipedia.org/wiki/Fractional_part
456 .. [2] https://mathworld.wolfram.com/FractionalPart.html
458 """
459 @classmethod
460 def eval(cls, arg):
461 from sympy.calculus.accumulationbounds import AccumBounds
463 def _eval(arg):
464 if arg in (S.Infinity, S.NegativeInfinity):
465 return AccumBounds(0, 1)
466 if arg.is_integer:
467 return S.Zero
468 if arg.is_number:
469 if arg is S.NaN:
470 return S.NaN
471 elif arg is S.ComplexInfinity:
472 return S.NaN
473 else:
474 return arg - floor(arg)
475 return cls(arg, evaluate=False)
477 terms = Add.make_args(arg)
478 real, imag = S.Zero, S.Zero
479 for t in terms:
480 # Two checks are needed for complex arguments
481 # see issue-7649 for details
482 if t.is_imaginary or (S.ImaginaryUnit*t).is_real:
483 i = im(t)
484 if not i.has(S.ImaginaryUnit):
485 imag += i
486 else:
487 real += t
488 else:
489 real += t
491 real = _eval(real)
492 imag = _eval(imag)
493 return real + S.ImaginaryUnit*imag
495 def _eval_rewrite_as_floor(self, arg, **kwargs):
496 return arg - floor(arg)
498 def _eval_rewrite_as_ceiling(self, arg, **kwargs):
499 return arg + ceiling(-arg)
501 def _eval_is_finite(self):
502 return True
504 def _eval_is_real(self):
505 return self.args[0].is_extended_real
507 def _eval_is_imaginary(self):
508 return self.args[0].is_imaginary
510 def _eval_is_integer(self):
511 return self.args[0].is_integer
513 def _eval_is_zero(self):
514 return fuzzy_or([self.args[0].is_zero, self.args[0].is_integer])
516 def _eval_is_negative(self):
517 return False
519 def __ge__(self, other):
520 if self.is_extended_real:
521 other = _sympify(other)
522 # Check if other <= 0
523 if other.is_extended_nonpositive:
524 return S.true
525 # Check if other >= 1
526 res = self._value_one_or_more(other)
527 if res is not None:
528 return not(res)
529 return Ge(self, other, evaluate=False)
531 def __gt__(self, other):
532 if self.is_extended_real:
533 other = _sympify(other)
534 # Check if other < 0
535 res = self._value_one_or_more(other)
536 if res is not None:
537 return not(res)
538 # Check if other >= 1
539 if other.is_extended_negative:
540 return S.true
541 return Gt(self, other, evaluate=False)
543 def __le__(self, other):
544 if self.is_extended_real:
545 other = _sympify(other)
546 # Check if other < 0
547 if other.is_extended_negative:
548 return S.false
549 # Check if other >= 1
550 res = self._value_one_or_more(other)
551 if res is not None:
552 return res
553 return Le(self, other, evaluate=False)
555 def __lt__(self, other):
556 if self.is_extended_real:
557 other = _sympify(other)
558 # Check if other <= 0
559 if other.is_extended_nonpositive:
560 return S.false
561 # Check if other >= 1
562 res = self._value_one_or_more(other)
563 if res is not None:
564 return res
565 return Lt(self, other, evaluate=False)
567 def _value_one_or_more(self, other):
568 if other.is_extended_real:
569 if other.is_number:
570 res = other >= 1
571 if res and not isinstance(res, Relational):
572 return S.true
573 if other.is_integer and other.is_positive:
574 return S.true
576 def _eval_as_leading_term(self, x, logx=None, cdir=0):
577 from sympy.calculus.accumulationbounds import AccumBounds
578 arg = self.args[0]
579 arg0 = arg.subs(x, 0)
580 r = self.subs(x, 0)
582 if arg0.is_finite:
583 if r.is_zero:
584 ndir = arg.dir(x, cdir=cdir)
585 if ndir.is_negative:
586 return S.One
587 return (arg - arg0).as_leading_term(x, logx=logx, cdir=cdir)
588 else:
589 return r
590 elif arg0 in (S.ComplexInfinity, S.Infinity, S.NegativeInfinity):
591 return AccumBounds(0, 1)
592 return arg.as_leading_term(x, logx=logx, cdir=cdir)
594 def _eval_nseries(self, x, n, logx, cdir=0):
595 from sympy.series.order import Order
596 arg = self.args[0]
597 arg0 = arg.subs(x, 0)
598 r = self.subs(x, 0)
600 if arg0.is_infinite:
601 from sympy.calculus.accumulationbounds import AccumBounds
602 o = Order(1, (x, 0)) if n <= 0 else AccumBounds(0, 1) + Order(x**n, (x, 0))
603 return o
604 else:
605 res = (arg - arg0)._eval_nseries(x, n, logx=logx, cdir=cdir)
606 if r.is_zero:
607 ndir = arg.dir(x, cdir=cdir)
608 res += S.One if ndir.is_negative else S.Zero
609 else:
610 res += r
611 return res
614@dispatch(frac, Basic) # type:ignore
615def _eval_is_eq(lhs, rhs): # noqa:F811
616 if (lhs.rewrite(floor) == rhs) or \
617 (lhs.rewrite(ceiling) == rhs):
618 return True
619 # Check if other < 0
620 if rhs.is_extended_negative:
621 return False
622 # Check if other >= 1
623 res = lhs._value_one_or_more(rhs)
624 if res is not None:
625 return False