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

1from typing import Tuple as tTuple 

2 

3from sympy.core.basic import Basic 

4from sympy.core.expr import Expr 

5 

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 

16 

17############################################################################### 

18######################### FLOOR and CEILING FUNCTIONS ######################### 

19############################################################################### 

20 

21 

22class RoundFunction(Function): 

23 """Abstract base class for rounding functions.""" 

24 

25 args: tTuple[Expr] 

26 

27 @classmethod 

28 def eval(cls, arg): 

29 v = cls._eval_number(arg) 

30 if v is not None: 

31 return v 

32 

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) 

40 

41 # Integral, numerical, symbolic part 

42 ipart = npart = spart = S.Zero 

43 

44 # Extract integral (or complex integral) terms 

45 terms = Add.make_args(arg) 

46 

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 

54 

55 if not (npart or spart): 

56 return ipart 

57 

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 

70 

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) 

80 

81 @classmethod 

82 def _eval_number(cls, arg): 

83 raise NotImplementedError() 

84 

85 def _eval_is_finite(self): 

86 return self.args[0].is_finite 

87 

88 def _eval_is_real(self): 

89 return self.args[0].is_real 

90 

91 def _eval_is_integer(self): 

92 return self.args[0].is_real 

93 

94 

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. 

101 

102 Examples 

103 ======== 

104 

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 

118 

119 See Also 

120 ======== 

121 

122 sympy.functions.elementary.integers.ceiling 

123 

124 References 

125 ========== 

126 

127 .. [1] "Concrete mathematics" by Graham, pp. 87 

128 .. [2] https://mathworld.wolfram.com/FloorFunction.html 

129 

130 """ 

131 _dir = -1 

132 

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] 

142 

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) 

158 

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 

177 

178 def _eval_is_negative(self): 

179 return self.args[0].is_negative 

180 

181 def _eval_is_nonnegative(self): 

182 return self.args[0].is_nonnegative 

183 

184 def _eval_rewrite_as_ceiling(self, arg, **kwargs): 

185 return -ceiling(-arg) 

186 

187 def _eval_rewrite_as_frac(self, arg, **kwargs): 

188 return arg - frac(arg) 

189 

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 

201 

202 return Le(self, other, evaluate=False) 

203 

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 

215 

216 return Ge(self, other, evaluate=False) 

217 

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 

229 

230 return Gt(self, other, evaluate=False) 

231 

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 

243 

244 return Lt(self, other, evaluate=False) 

245 

246 

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) 

251 

252 

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. 

259 

260 Examples 

261 ======== 

262 

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 

276 

277 See Also 

278 ======== 

279 

280 sympy.functions.elementary.integers.floor 

281 

282 References 

283 ========== 

284 

285 .. [1] "Concrete mathematics" by Graham, pp. 87 

286 .. [2] https://mathworld.wolfram.com/CeilingFunction.html 

287 

288 """ 

289 _dir = 1 

290 

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] 

300 

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) 

316 

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 

335 

336 def _eval_rewrite_as_floor(self, arg, **kwargs): 

337 return -floor(-arg) 

338 

339 def _eval_rewrite_as_frac(self, arg, **kwargs): 

340 return arg + frac(-arg) 

341 

342 def _eval_is_positive(self): 

343 return self.args[0].is_positive 

344 

345 def _eval_is_nonpositive(self): 

346 return self.args[0].is_nonpositive 

347 

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 

359 

360 return Lt(self, other, evaluate=False) 

361 

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 

373 

374 return Gt(self, other, evaluate=False) 

375 

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 

387 

388 return Ge(self, other, evaluate=False) 

389 

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 

401 

402 return Le(self, other, evaluate=False) 

403 

404 

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) 

408 

409 

410class frac(Function): 

411 r"""Represents the fractional part of x 

412 

413 For real numbers it is defined [1]_ as 

414 

415 .. math:: 

416 x - \left\lfloor{x}\right\rfloor 

417 

418 Examples 

419 ======== 

420 

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 

426 

427 returns zero for integer arguments 

428 

429 >>> n = Symbol('n', integer=True) 

430 >>> frac(n) 

431 0 

432 

433 rewrite as floor 

434 

435 >>> x = Symbol('x') 

436 >>> frac(x).rewrite(floor) 

437 x - floor(x) 

438 

439 for complex arguments 

440 

441 >>> r = Symbol('r', real=True) 

442 >>> t = Symbol('t', real=True) 

443 >>> frac(t + I*r) 

444 I*frac(r) + frac(t) 

445 

446 See Also 

447 ======== 

448 

449 sympy.functions.elementary.integers.floor 

450 sympy.functions.elementary.integers.ceiling 

451 

452 References 

453 =========== 

454 

455 .. [1] https://en.wikipedia.org/wiki/Fractional_part 

456 .. [2] https://mathworld.wolfram.com/FractionalPart.html 

457 

458 """ 

459 @classmethod 

460 def eval(cls, arg): 

461 from sympy.calculus.accumulationbounds import AccumBounds 

462 

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) 

476 

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 

490 

491 real = _eval(real) 

492 imag = _eval(imag) 

493 return real + S.ImaginaryUnit*imag 

494 

495 def _eval_rewrite_as_floor(self, arg, **kwargs): 

496 return arg - floor(arg) 

497 

498 def _eval_rewrite_as_ceiling(self, arg, **kwargs): 

499 return arg + ceiling(-arg) 

500 

501 def _eval_is_finite(self): 

502 return True 

503 

504 def _eval_is_real(self): 

505 return self.args[0].is_extended_real 

506 

507 def _eval_is_imaginary(self): 

508 return self.args[0].is_imaginary 

509 

510 def _eval_is_integer(self): 

511 return self.args[0].is_integer 

512 

513 def _eval_is_zero(self): 

514 return fuzzy_or([self.args[0].is_zero, self.args[0].is_integer]) 

515 

516 def _eval_is_negative(self): 

517 return False 

518 

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) 

530 

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) 

542 

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) 

554 

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) 

566 

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 

575 

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) 

581 

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) 

593 

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) 

599 

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 

612 

613 

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