decoint 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
decoint/functions.py ADDED
@@ -0,0 +1,564 @@
1
+ from .interval import Interval
2
+ from .rounding import add_up, add_down, sub_up, sub_down, mul_up, mul_down, div_up, div_down, sqrt_up, sqrt_down, exp_down, exp_up, log_up, log_down, pow_up, pow_down, root_up, root_down, sin_up, sin_down, tan_up, tan_down, asin_up, asin_down, acos_up, acos_down, atan_up, atan_down, sinh_up, sinh_down, cosh_up, cosh_down, tanh_up, tanh_down, atanh_up, atanh_down, asinh_up, asinh_down, acosh_up, acosh_down, cos_up, cos_down, sqr_up, sqr_down, pow_down_interval, pow_up_interval, exp2_up, exp2_down, log2_up, log2_down, exp10_up, exp10_down, log10_up, log10_down, atan2_down, atan2_up
3
+ from gmpy2 import mpfr, floor, ceil, context, get_context, is_zero, RoundDown, RoundUp, trunc
4
+ from .arithmetic import reciprocal
5
+ from .constants import PI, TWO_PI, HALF_PI
6
+ import builtins
7
+
8
+ Number = mpfr
9
+ ctx = get_context()
10
+ ctx.precision = 53
11
+ ctx.emin = -1073
12
+ ctx.emax = 1024
13
+
14
+ ZERO = mpfr(0)
15
+ with context(get_context(), round = RoundDown):
16
+ PI_DN = Number(PI)
17
+ HALF_PI_DN = Number(HALF_PI)
18
+ TWO_PI_DN = Number(TWO_PI)
19
+ with context(get_context(), round = RoundUp):
20
+ PI_UP = Number(PI)
21
+ HALF_PI_UP = Number(HALF_PI)
22
+ TWO_PI_UP = Number(TWO_PI)
23
+
24
+ MPI_DN = -PI_UP
25
+ MPI_UP = -PI_DN
26
+ MHALF_PI_DN = -HALF_PI_UP
27
+ MHALF_PI_UP = -HALF_PI_DN
28
+
29
+ def sqrt(x: Interval) -> Interval:
30
+ x = Interval._coerce(x)
31
+ if x.is_empty:
32
+ return Interval.empty()
33
+ if x.hi < 0:
34
+ return Interval.empty()
35
+ lo = max(x.lo, mpfr(0))
36
+ return Interval(sqrt_down(lo), sqrt_up(x.hi))
37
+
38
+ def exp(x) -> Interval:
39
+ x = Interval._coerce(x)
40
+ if x.is_empty:
41
+ return Interval.empty()
42
+ return Interval(exp_down(x.lo), exp_up(x.hi))
43
+
44
+ def log(x) -> Interval:
45
+ x = Interval._coerce(x)
46
+ if x.is_empty:
47
+ return Interval.empty()
48
+ if x.hi <= 0:
49
+ return Interval.empty()
50
+ if x.lo <= 0:
51
+ lo = mpfr('-inf')
52
+ else:
53
+ lo = log_down(x.lo)
54
+ hi = log_up(x.hi)
55
+ return Interval(lo, hi)
56
+
57
+ def pow_int(x, n):
58
+ x = Interval._coerce(x)
59
+ if x.is_empty:
60
+ return Interval.empty()
61
+ try:
62
+ n_int = int(n)
63
+ if n_int != n:
64
+ return Interval(mpfr('nan'), mpfr('nan'))
65
+ except Exception:
66
+ return Interval(mpfr('nan'), mpfr('nan'))
67
+ if n_int == 0:
68
+ return Interval(mpfr(1), mpfr(1))
69
+ if n_int < 0:
70
+ return reciprocal(pow_int(x, -n_int))
71
+ if n_int % 2 == 1:
72
+ lo = pow_down(x.lo, n_int)
73
+ hi = pow_up(x.hi, n_int)
74
+ return Interval(lo, hi)
75
+ if n_int % 2 == 0:
76
+ if x.lo >= 0:
77
+ lo = pow_down(x.lo, n_int)
78
+ hi = pow_up(x.hi, n_int)
79
+ return Interval(lo, hi)
80
+ if x.hi <= 0:
81
+ hi_sub = builtins.abs(x.hi)
82
+ lo_sub = builtins.abs(x.lo)
83
+ lo = pow_down(hi_sub, n_int)
84
+ hi = pow_up(lo_sub, n_int)
85
+ return Interval(lo, hi)
86
+ hi_sub = builtins.abs(x.hi)
87
+ lo_sub = builtins.abs(x.lo)
88
+ hi = max(
89
+ pow_up(hi_sub, n_int),
90
+ pow_up(lo_sub, n_int)
91
+ )
92
+ return Interval(mpfr(0), hi)
93
+
94
+ def sign(x) -> Interval:
95
+ x = Interval._coerce(x)
96
+ if x.is_empty:
97
+ return Interval.empty()
98
+ if x.lo > 0:
99
+ return Interval(Number(1), Number(1))
100
+ if x.hi < 0:
101
+ return Interval(Number(-1), Number(-1))
102
+ if x.lo == 0 and x.hi == 0:
103
+ return Interval(Number(0), Number(0))
104
+ if x.lo == 0:
105
+ return Interval(Number(0), Number(1))
106
+ if x.hi == 0:
107
+ return Interval(Number(-1), Number(0))
108
+ return Interval(Number(-1), Number(1))
109
+
110
+ def interval_min(x, y) -> Interval:
111
+ x = Interval._coerce(x)
112
+ y = Interval._coerce(y)
113
+ if x.is_empty or y.is_empty:
114
+ return Interval.empty()
115
+ return Interval(min(x.lo, y.lo), min(x.hi, y.hi))
116
+
117
+ def interval_max(x, y) -> Interval:
118
+ x = Interval._coerce(x)
119
+ y = Interval._coerce(y)
120
+
121
+ if x.is_empty or y.is_empty:
122
+ return Interval.empty()
123
+ return Interval(max(x.lo, y.lo), max(x.hi, y.hi))
124
+
125
+ def nth_root(x, n) -> Interval:
126
+ x = Interval._coerce(x)
127
+ try:
128
+ n_int = int(n)
129
+ if n_int != n:
130
+ return Interval(mpfr('nan'), mpfr('nan'))
131
+ except Exception:
132
+ return Interval(mpfr('nan'), mpfr('nan'))
133
+ if n_int <= 0:
134
+ return Interval(mpfr('nan'), mpfr('nan'))
135
+ if x.is_empty:
136
+ return Interval.empty()
137
+ if n_int % 2 == 1:
138
+ return Interval(root_down(x.lo, n), root_up(x.hi, n))
139
+ if x.hi < 0:
140
+ return Interval.empty()
141
+ lo = max(x.lo, mpfr(0))
142
+ return Interval(root_down(lo, n_int), root_up(x.hi, n_int))
143
+
144
+ def contains_periodic_point(x, offset, period, prec = 128):
145
+ with context(get_context(), precision = prec):
146
+ off_val = offset() if callable(offset) else offset
147
+ per_val = period() if callable(offset) else period
148
+
149
+ lo_mpfr = mpfr(str(x.lo))
150
+ hi_mpfr = mpfr(str(x.hi))
151
+ off_mpfr = mpfr(str(off_val))
152
+ per_mpfr = mpfr(str(per_val))
153
+
154
+ if hi_mpfr - lo_mpfr >= per_mpfr:
155
+ return True
156
+
157
+ k_lo = (lo_mpfr - off_mpfr) / per_mpfr
158
+ k_hi = (hi_mpfr - off_mpfr) / per_mpfr
159
+
160
+ return ceil(k_lo) <= floor(k_hi)
161
+ def sin(x) -> Interval:
162
+ x = Interval._coerce(x)
163
+ if x.is_empty:
164
+ return Interval.empty()
165
+ if x.width >= TWO_PI:
166
+ return Interval(mpfr(-1), mpfr(1))
167
+ s1 = sin_down(x.lo)
168
+ s2 = sin_down(x.hi)
169
+ t1 = sin_up(x.lo)
170
+ t2 = sin_up(x.hi)
171
+ lo = min(s1, s2)
172
+ hi = max(t1, t2)
173
+
174
+ if contains_periodic_point(x, HALF_PI, TWO_PI):
175
+ hi = mpfr(1)
176
+ if contains_periodic_point(x, -HALF_PI, TWO_PI):
177
+ lo = mpfr(-1)
178
+ return Interval(lo, hi)
179
+
180
+ def cos(x) -> Interval:
181
+ x = Interval._coerce(x)
182
+ if x.is_empty:
183
+ return Interval.empty()
184
+ if x.width >= TWO_PI:
185
+ return Interval(mpfr(-1), mpfr(1))
186
+
187
+ c1 = cos_down(x.lo)
188
+ c2 = cos_down(x.hi)
189
+ d1 = cos_up(x.lo)
190
+ d2 = cos_up(x.hi)
191
+ lo = min(c1, c2)
192
+ hi = max(d1, d2)
193
+
194
+ # Cosine peaks at 0 (mod 2pi), troughs at pi (mod 2pi)
195
+ if contains_periodic_point(x, mpfr(0), TWO_PI):
196
+ hi = mpfr(1)
197
+ if contains_periodic_point(x, PI, TWO_PI):
198
+ lo = mpfr(-1)
199
+ return Interval(lo, hi)
200
+
201
+ def tan(x):
202
+ x = Interval._coerce(x)
203
+ if x.is_empty:
204
+ return Interval.empty()
205
+ if x.width >= PI:
206
+ return Interval.entire()
207
+ if contains_periodic_point(x, HALF_PI, PI):
208
+ return Interval.entire()
209
+ lo = tan_down(x.lo)
210
+ hi = tan_up(x.hi)
211
+ return Interval(lo, hi)
212
+
213
+ def asin(x):
214
+ x = Interval._coerce(x)
215
+ if x.is_empty:
216
+ return Interval.empty()
217
+ x = x.intersection(Interval(-1, 1))
218
+
219
+ if x.is_empty:
220
+ return Interval.empty()
221
+ return Interval(asin_down(x.lo), asin_up(x.hi))
222
+
223
+ def acos(x):
224
+ x = Interval._coerce(x)
225
+ if x.is_empty:
226
+ return Interval.empty()
227
+ x = x.intersection(Interval(-1, 1))
228
+ if x.is_empty:
229
+ return Interval.empty()
230
+ return Interval(acos_down(x.hi), acos_up(x.lo))
231
+
232
+ def atan(x):
233
+ x = Interval._coerce(x)
234
+
235
+ if x.is_empty:
236
+ return Interval.empty()
237
+
238
+ return Interval(
239
+ atan_down(x.lo),
240
+ atan_up(x.hi)
241
+ )
242
+
243
+ def sinh(x):
244
+ x = Interval._coerce(x)
245
+ if x.is_empty:
246
+ return Interval.empty()
247
+ return Interval(sinh_down(x.lo), sinh_up(x.hi))
248
+
249
+ def tanh(x):
250
+ x = Interval._coerce(x)
251
+ if x.is_empty:
252
+ return Interval.empty()
253
+ return Interval(tanh_down(x.lo), tanh_up(x.hi))
254
+
255
+ def cosh(x):
256
+ x = Interval._coerce(x)
257
+ if x.is_empty:
258
+ return Interval.empty()
259
+
260
+ if x.lo >= 0:
261
+ return Interval(cosh_down(x.lo), cosh_up(x.hi))
262
+ if x.hi <= 0:
263
+ return Interval(cosh_down(x.hi), cosh_up(x.lo))
264
+ return Interval(mpfr(1), max(cosh_up(x.lo), cosh_up(x.hi)))
265
+
266
+ def asinh(x) -> Interval:
267
+ x = Interval._coerce(x)
268
+ if x.is_empty:
269
+ return Interval.empty()
270
+ return Interval(asinh_down(x.lo), asinh_up(x.hi))
271
+
272
+ def acosh(x) -> Interval:
273
+ x = Interval._coerce(x)
274
+ if x.is_empty:
275
+ return Interval.empty()
276
+ if x.hi < 1:
277
+ return Interval.empty()
278
+
279
+
280
+ lo = max(x.lo, mpfr(1))
281
+ return Interval(acosh_down(lo), acosh_up(x.hi))
282
+
283
+ def atanh(x) -> Interval:
284
+ x = Interval._coerce(x)
285
+ if x.is_empty:
286
+ return Interval.empty()
287
+
288
+ x = x.intersection(Interval(-1, 1))
289
+ if x.is_empty:
290
+ return Interval.empty()
291
+
292
+ lo = mpfr('-inf') if x.lo <= -1 else atanh_down(x.lo)
293
+ hi = mpfr('inf') if x.hi >= 1 else atanh_up(x.hi)
294
+
295
+ return Interval(lo, hi)
296
+
297
+ def abs(x):
298
+ x = Interval._coerce(x)
299
+ if x.is_empty:
300
+ return Interval.empty()
301
+ if x.lo >= 0:
302
+ return Interval(x.lo, x.hi)
303
+ elif x.hi <= 0:
304
+ return Interval(-x.hi, -x.lo)
305
+
306
+ hi = max(-x.lo, x.hi)
307
+ return Interval(Number(0), hi)
308
+
309
+ def safe_atan2_dn(y, x):
310
+ y_val = ZERO if is_zero(y) else y
311
+ x_val = ZERO if is_zero(x) else x
312
+ return atan2_down(y_val, x_val)
313
+
314
+ def safe_atan2_up(y, x):
315
+ y_val = ZERO if is_zero(y) else y
316
+ x_val = ZERO if is_zero(x) else x
317
+ return atan2_up(y_val, x_val)
318
+
319
+ def atan2(y, x):
320
+ y = Interval._coerce(y)
321
+ x = Interval._coerce(x)
322
+
323
+ # 1. Empty set handling
324
+ if y.is_empty or x.is_empty:
325
+ return Interval.empty()
326
+
327
+ # 2. Undefined origin point (0, 0) handling
328
+ if y.lo == ZERO and y.hi == ZERO and x.lo == ZERO and x.hi == ZERO:
329
+ return Interval.empty()
330
+
331
+ y1, y2 = y.lo, y.hi
332
+ x1, x2 = x.lo, x.hi
333
+
334
+ # Case 1: X is strictly positive (x1 > 0)
335
+ if x1 > ZERO:
336
+ if y1 >= ZERO:
337
+ lo = safe_atan2_dn(y1, x2)
338
+ hi = safe_atan2_up(y2, x1)
339
+ elif y2 <= ZERO:
340
+ lo = safe_atan2_dn(y1, x1)
341
+ hi = safe_atan2_up(y2, x2)
342
+ else:
343
+ lo = safe_atan2_dn(y1, x1)
344
+ hi = safe_atan2_up(y2, x1)
345
+
346
+ # Case 2: X is strictly negative (x2 < 0)
347
+ elif x2 < ZERO:
348
+ if y1 > ZERO:
349
+ lo = safe_atan2_dn(y2, x2)
350
+ hi = safe_atan2_up(y1, x1)
351
+ elif y2 < ZERO:
352
+ lo = safe_atan2_dn(y2, x1)
353
+ hi = safe_atan2_up(y1, x2)
354
+ elif y1 == ZERO and y2 == ZERO:
355
+ lo, hi = PI_DN, PI_UP
356
+ elif y1 == ZERO and y2 > ZERO:
357
+ lo = safe_atan2_dn(y2, x2)
358
+ hi = PI_UP
359
+ else: # y1 < ZERO and y2 >= ZERO
360
+ lo, hi = MPI_DN, PI_UP
361
+
362
+ # Case 3: X contains zero (x1 <= 0 <= x2)
363
+ else:
364
+ # Subcase 3.0: X is degenerate [0, 0]
365
+ if x1 == ZERO and x2 == ZERO:
366
+ if y1 >= ZERO:
367
+ lo, hi = HALF_PI_DN, HALF_PI_UP
368
+ elif y2 <= ZERO:
369
+ lo, hi = MHALF_PI_DN, MHALF_PI_UP
370
+ else: # y1 < ZERO and y2 > ZERO
371
+ lo, hi = MHALF_PI_DN, HALF_PI_UP
372
+
373
+ # Subcase 3.1: Y is strictly positive
374
+ elif y1 > ZERO:
375
+ lo = safe_atan2_dn(y1, x2)
376
+ hi = safe_atan2_up(y1, x1)
377
+
378
+ # Subcase 3.2: Y is strictly negative
379
+ elif y2 < ZERO:
380
+ lo = safe_atan2_dn(y2, x1)
381
+ hi = safe_atan2_up(y2, x2)
382
+
383
+ # Subcase 3.3: Y contains zero (y1 <= 0 <= y2) and X is non-degenerate
384
+ else:
385
+ if x1 < ZERO:
386
+ if y1 < ZERO and y2 > ZERO:
387
+ lo, hi = MPI_DN, PI_UP
388
+ elif y1 == ZERO and y2 == ZERO:
389
+ if x2 > ZERO:
390
+ lo, hi = ZERO, PI_UP
391
+ else: # x2 == ZERO
392
+ lo, hi = PI_DN, PI_UP
393
+ elif y1 == ZERO and y2 > ZERO:
394
+ if x2 > ZERO:
395
+ lo, hi = ZERO, PI_UP
396
+ else: # x2 == ZERO
397
+ lo, hi = HALF_PI_DN, PI_UP
398
+ else: # y1 < ZERO and y2 == ZERO
399
+ lo, hi = MPI_DN, PI_UP
400
+ else: # x1 == ZERO and x2 > ZERO
401
+ if y1 < ZERO and y2 > ZERO:
402
+ lo, hi = MHALF_PI_DN, HALF_PI_UP
403
+ elif y1 == ZERO and y2 == ZERO:
404
+ lo, hi = ZERO, ZERO
405
+ elif y1 == ZERO and y2 > ZERO:
406
+ lo, hi = ZERO, HALF_PI_UP
407
+ else: # y1 < ZERO and y2 == ZERO
408
+ lo, hi = MHALF_PI_DN, ZERO
409
+
410
+ return Interval(lo, hi)
411
+
412
+
413
+
414
+ def sqr(x):
415
+ x = Interval._coerce(x)
416
+ if x.is_empty:
417
+ return Interval.empty()
418
+ if x.lo >= 0:
419
+ lo = sqr_down(x.lo)
420
+ hi = sqr_up(x.hi)
421
+ return Interval(lo, hi)
422
+ elif x.hi <= 0:
423
+ lo = sqr_down(builtins.abs(x.hi))
424
+ hi = sqr_up(builtins.abs(x.lo))
425
+ return Interval(lo, hi)
426
+ hi = max(sqr_up(builtins.abs(x.hi)), sqr_up(builtins.abs(x.lo)))
427
+ return Interval(mpfr(0), hi)
428
+
429
+ def pow_interval(x, y):
430
+ x = Interval._coerce(x)
431
+ y = Interval._coerce(y)
432
+
433
+ if x.is_empty or y.is_empty:
434
+ return Interval.empty()
435
+
436
+ x_lo = max(0, x.lo)
437
+ x_hi = x.hi
438
+
439
+ if x_lo > x_hi:
440
+ return Interval.empty()
441
+
442
+ if x_hi == 0:
443
+ if y.hi <= 0:
444
+ return Interval.empty()
445
+ return Interval(mpfr(0), mpfr(0))
446
+
447
+ if x_lo == 0:
448
+ v_down = [
449
+ pow_down_interval(x_hi, y.lo),
450
+ pow_down_interval(x_hi, y.hi)
451
+ ]
452
+ v_up = [
453
+ pow_up_interval(x_hi, y.lo),
454
+ pow_up_interval(x_hi, y.hi)
455
+ ]
456
+
457
+ if y.lo > 0 or y.hi > 0:
458
+ v_down.append(mpfr(0))
459
+ v_up.append(mpfr(0))
460
+
461
+ if y.lo < 0 or y.hi < 0:
462
+ v_down.append(mpfr('inf'))
463
+ v_up.append(mpfr('inf'))
464
+
465
+ if y.lo <= 0 <= y.hi:
466
+ v_down.append(mpfr(1))
467
+ v_up.append(mpfr(1))
468
+
469
+ else:
470
+ v_down = [
471
+ pow_down_interval(x_lo, y.lo),
472
+ pow_down_interval(x_lo, y.hi),
473
+ pow_down_interval(x_hi, y.lo),
474
+ pow_down_interval(x_hi, y.hi)
475
+ ]
476
+ v_up = [
477
+ pow_up_interval(x_lo, y.lo),
478
+ pow_up_interval(x_lo, y.hi),
479
+ pow_up_interval(x_hi, y.lo),
480
+ pow_up_interval(x_hi, y.hi)
481
+ ]
482
+
483
+ return Interval(min(v_down), max(v_up))
484
+
485
+ def exp2(x):
486
+ x = Interval._coerce(x)
487
+
488
+ if x.is_empty:
489
+ return Interval.empty()
490
+
491
+ return Interval(exp2_down(x.lo), exp2_up(x.hi))
492
+
493
+ def exp10(x):
494
+ x = Interval._coerce(x)
495
+
496
+ if x.is_empty:
497
+ return Interval.empty()
498
+
499
+ return Interval(exp10_down(x.lo), exp10_up(x.hi))
500
+
501
+ def log2(x):
502
+ x = Interval._coerce(x)
503
+ if x.is_empty:
504
+ return Interval.empty()
505
+ if x.hi <= 0:
506
+ return Interval.empty()
507
+ if x.lo <= 0:
508
+ lo = mpfr('-inf')
509
+ else:
510
+ lo = log2_down(x.lo)
511
+ hi = log2_up(x.hi)
512
+ return Interval(lo, hi)
513
+
514
+ def log10(x):
515
+ x = Interval._coerce(x)
516
+ if x.is_empty:
517
+ return Interval.empty()
518
+ if x.hi <= 0:
519
+ return Interval.empty()
520
+ if x.lo <= 0:
521
+ lo = mpfr('-inf')
522
+ else:
523
+ lo = log10_down(x.lo)
524
+ hi = log10_up(x.hi)
525
+ return Interval(lo, hi)
526
+
527
+ def interval_ceil(x):
528
+ if x.is_empty:
529
+ return Interval.empty()
530
+ if x.lo == mpfr('-inf'):
531
+ lo = mpfr('-inf')
532
+ else:
533
+ lo = ceil(x.lo)
534
+ if x.hi == mpfr('inf'):
535
+ hi = mpfr('inf')
536
+ else:
537
+ hi = ceil(x.hi)
538
+ return Interval(lo, hi)
539
+
540
+ def interval_floor(x):
541
+ if x.is_empty:
542
+ return Interval.empty()
543
+ if x.lo == mpfr('-inf'):
544
+ lo = mpfr('-inf')
545
+ else:
546
+ lo = floor(x.lo)
547
+ if x.hi == mpfr('inf'):
548
+ hi = mpfr('inf')
549
+ else:
550
+ hi = floor(x.hi)
551
+ return Interval(lo, hi)
552
+
553
+ def interval_trunc(x):
554
+ if x.is_empty:
555
+ return Interval.empty()
556
+ if x.lo == mpfr('-inf'):
557
+ lo = mpfr('-inf')
558
+ else:
559
+ lo = trunc(x.lo)
560
+ if x.hi == mpfr('inf'):
561
+ hi = mpfr('inf')
562
+ else:
563
+ hi = trunc(x.hi)
564
+ return Interval(lo, hi)