integers.py 22 KB

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