bigdecimal-pure 0.1.0 → 0.2.0

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.
@@ -0,0 +1,661 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'calc'
4
+
5
+ module BigDecimalPure
6
+ # Argument handling shared by BigMath, power and sqrt, with the messages of
7
+ # bigdecimal 4.x.
8
+ module Calc # :nodoc: all
9
+ module_function
10
+
11
+ def precision(prec, name, accept_zero: false)
12
+ unless prec.is_a?(Integer)
13
+ original = prec
14
+ raise TypeError, "no implicit conversion of #{original.class} into Integer" unless prec.respond_to?(:to_int)
15
+
16
+ prec = prec.to_int
17
+ raise TypeError, "can't convert #{original.class} to Integer" unless prec.is_a?(Integer)
18
+ end
19
+ if accept_zero
20
+ raise ArgumentError, "Negative precision for #{name}" if prec.negative?
21
+ elsif prec <= 0
22
+ raise ArgumentError, "Zero or negative precision for #{name}"
23
+ end
24
+ prec
25
+ end
26
+
27
+ def coerce(x, prec, _name)
28
+ case x
29
+ when BigDecimal then x
30
+ when Integer, Float then BigDecimal(x, 0)
31
+ when Rational then BigDecimal(x, [prec, 32].max)
32
+ else raise ArgumentError, "#{x.inspect} can't be coerced into BigDecimal"
33
+ end
34
+ end
35
+
36
+ def nan_result
37
+ BigDecimal._raise_if(BigDecimal::EXCEPTION_NaN, "Computation results to 'NaN'")
38
+ BigDecimal::NAN
39
+ end
40
+
41
+ def infinity_result
42
+ BigDecimal._raise_if(BigDecimal::EXCEPTION_INFINITY, "Computation results in 'Infinity'")
43
+ BigDecimal::INFINITY
44
+ end
45
+
46
+ def underflow_result
47
+ BigDecimal._raise_if(BigDecimal::EXCEPTION_UNDERFLOW, 'Exponent underflow')
48
+ BigDecimal(0)
49
+ end
50
+
51
+ # A positive value below every digit a result of w digits can show:
52
+ # 1.sub(tiny, prec) rounds like 1 minus anything that small.
53
+ def tiny(w) = BigDecimal._make(1, 1, -(w + 10))
54
+
55
+ # atan of any finite x.
56
+ def signed_atan(x, w)
57
+ return x if x.zero?
58
+
59
+ x.negative? ? -atan(-x, w) : atan(x, w)
60
+ end
61
+
62
+ # asin of a finite x with 0 < |x| < 1: atan(x / sqrt(1 - x**2)).
63
+ def asin(x, w)
64
+ signed_atan(x.div(sqrt(minus(one, times(x, x)), w + 2), w + 2), w)
65
+ end
66
+
67
+ def log_base(x, prec, base, name)
68
+ prec = precision(prec, name)
69
+ x = coerce(x, prec, name)
70
+ return nan_result if x.nan?
71
+ return infinity_result if x.infinite? == 1
72
+
73
+ # As bigdecimal 4.x: round half up well below the precision first, so
74
+ # that log10(10**n) is n in every rounding mode.
75
+ w = prec + 24
76
+ v = BigMath.log(x, w).div(BigMath.log(BigDecimal(base), w), w)
77
+ v = v.round(prec + 16 - (v.exponent.negative? ? v.exponent : 0), BigDecimal::ROUND_HALF_UP)
78
+ v.mult(1, prec)
79
+ end
80
+
81
+ # gamma and lgamma raise for NaN and the infinities, whatever the mode.
82
+ def gamma_finite!(x)
83
+ raise FloatDomainError, "Computation results in 'NaN' (Not a Number)" if x.nan?
84
+ raise FloatDomainError, "Computation results in 'Infinity'" if x.infinite?
85
+ end
86
+
87
+ FACTORIAL_LIMIT = 3000
88
+
89
+ # Gamma(x) for a finite x >= 0.5, to w digits.
90
+ def gamma_positive(x, w)
91
+ return BigDecimal(factorial(x.to_i - 1)) if x.frac.zero? && x <= FACTORIAL_LIMIT
92
+
93
+ exp(lgamma_positive(x, w + 2), w)
94
+ end
95
+
96
+ # ln |Gamma(x)| and its sign for a finite x < 0.5 that is not an
97
+ # integer, by the reflection formula
98
+ # ln |Gamma(x)| = ln pi - ln |sin(pi x)| - ln Gamma(1 - x),
99
+ # with as many digits after the point as w significant digits need.
100
+ def lgamma_reflected(x, w)
101
+ digits = w
102
+ loop do
103
+ s = digits + 5
104
+ sine = sin_pi(x, s + 5)
105
+ log_sine = log(sine.abs, s + 5 + ndigits((3 * sine.exponent.abs) + 1))
106
+ log_pi = log(pi_decimal(s + 5), s + 5)
107
+ value = fixed(log_pi, s) - fixed(log_sine, s) - fixed(lgamma_positive(minus(one, x), s), s)
108
+ result = decimal(value, s)
109
+ return [result, sine.positive? ? 1 : -1] if !value.zero? && digits + result.exponent >= w
110
+
111
+ digits += w - (digits + (value.zero? ? -digits : result.exponent)) + 5
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ # power, ** and sqrt, which need the arithmetic of BigDecimalPure::Calc.
118
+ class BigDecimal
119
+ def **(other)
120
+ case other
121
+ when BigDecimal, Integer, Float, Rational then power(other)
122
+ when nil then raise TypeError, 'wrong argument type NilClass'
123
+ else
124
+ x, y = other.coerce(self)
125
+ x**y
126
+ end
127
+ end
128
+
129
+ # self ** y to prec digits; prec == 0 with an Integer y and no limit is
130
+ # exact, as in bigdecimal 4.x, and so are the other rules below.
131
+ def power(y, prec = 0)
132
+ calc = BigDecimalPure::Calc
133
+ prec = calc.precision(prec, :power, accept_zero: true)
134
+ x = self
135
+ y = calc.coerce(y, prec.nonzero? || n_significant_digits, :power)
136
+ return calc.nan_result if x.nan? || y.nan?
137
+ return BigDecimal(1) if y.zero?
138
+
139
+ if y.infinite?
140
+ if x.negative?
141
+ return BigDecimal(0) if x < -1 && y.negative?
142
+ return BigDecimal(0) if x > -1 && y.positive?
143
+
144
+ raise Math::DomainError, 'Result undefined for negative base raised to infinite power'
145
+ elsif x < 1
146
+ return y.positive? ? BigDecimal(0) : calc.infinity_result
147
+ elsif x == 1
148
+ return BigDecimal(1)
149
+ else
150
+ return y.positive? ? calc.infinity_result : BigDecimal(0)
151
+ end
152
+ end
153
+ if x.infinite? && y.negative?
154
+ odd = x.negative? && y.frac.zero? && (y % 2).nonzero? # BigDecimal has no odd?
155
+ return odd ? -BigDecimal(0) : BigDecimal(0)
156
+ end
157
+ if x.zero?
158
+ return BigDecimal(0) if y.positive?
159
+ return -calc.infinity_result if y.frac.zero? && (y % 2).nonzero? && x.sign == SIGN_NEGATIVE_ZERO
160
+
161
+ return calc.infinity_result
162
+ end
163
+ if x.negative?
164
+ raise Math::DomainError, 'Computation results in complex number' unless y.frac.zero?
165
+
166
+ result = (-x).power(y, prec)
167
+ return (y % 2).zero? ? result : -result
168
+ end
169
+ return BigDecimal(1) if x == 1
170
+
171
+ limit = BigDecimal.limit
172
+ integral = y.frac.zero?
173
+ return _power_exact(y.to_i) if integral && prec.zero? && limit.zero?
174
+
175
+ digits = prec.nonzero? || ([x.n_significant_digits, y.n_significant_digits, DOUBLE_FIG].max + DOUBLE_FIG)
176
+ digits = [digits, limit].min if prec.zero? && limit.nonzero?
177
+ work = digits + DOUBLE_FIG
178
+ if y.negative?
179
+ inverse = x.power(-y, work)
180
+ return BigDecimal(0) if inverse.infinite?
181
+ return calc.infinity_result if inverse.zero?
182
+
183
+ return BigDecimal(1).div(inverse, digits)
184
+ end
185
+ if integral && y.exponent < (Math.log(digits) * 5) + 20
186
+ return _power_by_squaring(y.to_i, work + y.exponent).mult(1, digits)
187
+ end
188
+
189
+ if x > 1 && x.finite?
190
+ log_exponent = x < 2 ? (x - 1).exponent : Math.log10(x.exponent).round
191
+ work += [y.exponent + log_exponent, 0].max
192
+ end
193
+ BigMath.exp(BigMath.log(x, work).mult(y, work), digits)
194
+ end
195
+
196
+ # Square root to prec digits, prec == 0 meaning the significant digits
197
+ # plus 16 (at most BigDecimal.limit). Exact results stay exact in every
198
+ # rounding mode: the root is an Integer square root.
199
+ def sqrt(prec)
200
+ calc = BigDecimalPure::Calc
201
+ prec = calc.precision(prec, :sqrt, accept_zero: true)
202
+ return calc.infinity_result if infinite? == 1
203
+ raise FloatDomainError, 'sqrt of negative value' if negative?
204
+ raise FloatDomainError, "sqrt of 'NaN'(Not a Number)" if nan?
205
+ return self if zero?
206
+
207
+ if prec.zero?
208
+ limit = BigDecimal.limit
209
+ prec = n_significant_digits + DOUBLE_FIG
210
+ prec = [limit, prec].min if limit.nonzero?
211
+ end
212
+ root, exp, exact = calc.sqrt_digits(self, prec + 1)
213
+ BigDecimal._round_sig(1, root, exp, prec, BigDecimal._rounding_mode, sticky: !exact)
214
+ end
215
+
216
+ private
217
+
218
+ # self ** n exactly, for a finite self > 0; Infinity or 0 when the result
219
+ # leaves the exponent range.
220
+ def _power_exact(n)
221
+ return BigDecimal(1) * self if infinite? # Infinity ** n for n > 0
222
+
223
+ negative = n.negative?
224
+ n = n.abs
225
+ size = _e10 + Math.log10("0.#{@coef.to_s[0, 17]}".to_f)
226
+ if size.nonzero? && n * size.abs > 9e18
227
+ grows = size.positive? ^ negative
228
+ return grows ? BigDecimalPure::Calc.infinity_result : BigDecimal(0)
229
+ end
230
+ coef = 1
231
+ base = @coef
232
+ k = n
233
+ while k.positive?
234
+ coef *= base if k.odd?
235
+ k >>= 1
236
+ base *= base if k.positive?
237
+ end
238
+ result = BigDecimal._make(1, coef, @exp * n)
239
+ negative ? BigDecimal(1) / result : result
240
+ end
241
+
242
+ def _power_by_squaring(n, digits)
243
+ result = BigDecimal(1)
244
+ base = self
245
+ loop do
246
+ result = result.mult(base, digits) if n.odd?
247
+ n >>= 1
248
+ break if n.zero?
249
+
250
+ base = base.mult(base, digits)
251
+ end
252
+ result
253
+ end
254
+ end
255
+
256
+ # BigMath of bigdecimal 4.x, computed to the precision asked for.
257
+ module BigMath
258
+ CALC = BigDecimalPure::Calc
259
+ private_constant :CALC
260
+
261
+ module_function
262
+
263
+ def exp(x, prec)
264
+ prec = CALC.precision(prec, :exp)
265
+ x = CALC.coerce(x, prec, :exp)
266
+ return CALC.nan_result if x.nan?
267
+
268
+ if x.infinite? || x.exponent >= 21
269
+ return CALC.infinity_result if x.positive?
270
+ return BigDecimal(0) if x.infinite?
271
+
272
+ return CALC.underflow_result
273
+ end
274
+ return BigDecimal(1) if x.zero?
275
+
276
+ CALC.exp(x, prec + CALC::GUARD).mult(1, prec)
277
+ end
278
+
279
+ def log(x, prec)
280
+ prec = CALC.precision(prec, :log)
281
+ raise Math::DomainError, 'Complex argument for BigMath.log' if x.is_a?(Complex)
282
+
283
+ x = CALC.coerce(x, prec, :log)
284
+ return CALC.nan_result if x.nan?
285
+ raise Math::DomainError, 'Negative argument for log' if x.negative?
286
+ return -CALC.infinity_result if x.zero?
287
+ return CALC.infinity_result if x.infinite?
288
+ return BigDecimal(0) if x == 1
289
+
290
+ CALC.log(x, prec + CALC::GUARD).mult(1, prec)
291
+ end
292
+
293
+ def sqrt(x, prec)
294
+ prec = CALC.precision(prec, :sqrt)
295
+ CALC.coerce(x, prec, :sqrt).sqrt(prec)
296
+ end
297
+
298
+ def cbrt(x, prec)
299
+ prec = CALC.precision(prec, :cbrt)
300
+ x = CALC.coerce(x, prec, :cbrt)
301
+ return CALC.nan_result if x.nan?
302
+ return CALC.infinity_result * x.infinite? if x.infinite?
303
+ return BigDecimal(0) if x.zero?
304
+
305
+ root, exp, exact = CALC.cbrt_digits(x.abs, prec + 1)
306
+ BigDecimal._round_sig(x.negative? ? -1 : 1, root, exp, prec, BigDecimal._rounding_mode, sticky: !exact)
307
+ end
308
+
309
+ def hypot(x, y, prec)
310
+ prec = CALC.precision(prec, :hypot)
311
+ x = CALC.coerce(x, prec, :hypot)
312
+ y = CALC.coerce(y, prec, :hypot)
313
+ return CALC.nan_result if x.nan? || y.nan?
314
+ return CALC.infinity_result if x.infinite? || y.infinite?
315
+
316
+ CALC.plus(CALC.times(x, x), CALC.times(y, y)).sqrt(prec)
317
+ end
318
+
319
+ def sin(x, prec)
320
+ prec = CALC.precision(prec, :sin)
321
+ x = CALC.coerce(x, prec, :sin)
322
+ return CALC.nan_result if x.infinite? || x.nan?
323
+ return BigDecimal(0) if x.zero?
324
+
325
+ CALC.sin_cos(x, prec + CALC::GUARD, false).mult(1, prec)
326
+ end
327
+
328
+ def cos(x, prec)
329
+ prec = CALC.precision(prec, :cos)
330
+ x = CALC.coerce(x, prec, :cos)
331
+ return CALC.nan_result if x.infinite? || x.nan?
332
+ return BigDecimal(1) if x.zero?
333
+
334
+ CALC.sin_cos(x, prec + CALC::GUARD, true).mult(1, prec)
335
+ end
336
+
337
+ def tan(x, prec)
338
+ prec = CALC.precision(prec, :tan)
339
+ x = CALC.coerce(x, prec + 16, :tan)
340
+ return CALC.nan_result if x.infinite? || x.nan?
341
+ return BigDecimal(0) if x.zero?
342
+
343
+ CALC.tan(x, prec + CALC::GUARD).mult(1, prec)
344
+ end
345
+
346
+ def asin(x, prec)
347
+ prec = CALC.precision(prec, :asin)
348
+ x = CALC.coerce(x, prec, :asin)
349
+ raise Math::DomainError, 'Out of domain argument for asin' if x < -1 || x > 1
350
+ return CALC.nan_result if x.nan?
351
+ return x.mult(1, prec) if x.zero?
352
+
353
+ w = prec + CALC::GUARD
354
+ return CALC.pi_decimal(w).div(x.positive? ? 2 : -2, prec) if x.abs == 1
355
+
356
+ CALC.asin(x, w).mult(1, prec)
357
+ end
358
+
359
+ def acos(x, prec)
360
+ prec = CALC.precision(prec, :acos)
361
+ x = CALC.coerce(x, prec, :acos)
362
+ raise Math::DomainError, 'Out of domain argument for acos' if x < -1 || x > 1
363
+ return CALC.nan_result if x.nan?
364
+
365
+ w = prec + CALC::GUARD
366
+ half_pi = CALC.pi_decimal(w).div(2, w)
367
+ return half_pi.mult(1, prec) if x.zero?
368
+ return BigDecimal(0) if x == 1
369
+ return half_pi.mult(2, prec) if x == -1
370
+ # pi/2 - asin(x) cancels for x near 1: use atan(sqrt(1 - x**2) / x).
371
+ return CALC.plus(half_pi, CALC.asin(-x, w)).mult(1, prec) if x.negative?
372
+
373
+ sine = CALC.sqrt(CALC.minus(CALC.one, CALC.times(x, x)), w + 2)
374
+ CALC.atan(sine.div(x, w + 2), w).mult(1, prec)
375
+ end
376
+
377
+ def atan(x, prec)
378
+ prec = CALC.precision(prec, :atan)
379
+ x = CALC.coerce(x, prec, :atan)
380
+ return CALC.nan_result if x.nan?
381
+
382
+ w = prec + CALC::GUARD
383
+ return CALC.pi_decimal(w).div(2 * x.infinite?, prec) if x.infinite?
384
+
385
+ CALC.signed_atan(x, w).mult(1, prec)
386
+ end
387
+
388
+ def atan2(y, x, prec)
389
+ prec = CALC.precision(prec, :atan2)
390
+ x = CALC.coerce(x, prec, :atan2)
391
+ y = CALC.coerce(y, prec, :atan2)
392
+ return CALC.nan_result if x.nan? || y.nan?
393
+
394
+ if x.infinite? || y.infinite?
395
+ one = BigDecimal(1)
396
+ zero = BigDecimal(0)
397
+ x = if x.infinite?
398
+ x.positive? ? one : -one
399
+ else
400
+ zero
401
+ end
402
+ y = if y.infinite?
403
+ y.positive? ? one : -one
404
+ else
405
+ y.sign * zero
406
+ end
407
+ end
408
+ return x.sign >= 0 ? BigDecimal(0) : y.sign * PI(prec) if y.zero?
409
+
410
+ w = prec + CALC::GUARD
411
+ negative = y.negative?
412
+ y = -y if negative
413
+ pi = CALC.pi_decimal(w)
414
+ half_pi = pi.div(2, w)
415
+ value = if x.zero?
416
+ half_pi
417
+ elsif x.positive?
418
+ y < x ? CALC.atan(y.div(x, w), w) : CALC.minus(half_pi, CALC.atan(x.div(y, w), w))
419
+ elsif y < -x
420
+ CALC.minus(pi, CALC.atan(y.div(-x, w), w))
421
+ else
422
+ CALC.plus(half_pi, CALC.atan((-x).div(y, w), w))
423
+ end
424
+ (negative ? -value : value).mult(1, prec)
425
+ end
426
+
427
+ def sinh(x, prec)
428
+ prec = CALC.precision(prec, :sinh)
429
+ x = CALC.coerce(x, prec, :sinh)
430
+ return CALC.nan_result if x.nan?
431
+ return CALC.infinity_result * x.infinite? if x.infinite?
432
+ return BigDecimal(0) if x.zero?
433
+
434
+ w = prec + CALC::GUARD
435
+ return CALC.sinh_small(x, w).mult(1, prec) if x.abs < 1
436
+
437
+ if x.exponent >= 21
438
+ e = exp(x, prec + 16)
439
+ return (e - BigDecimal(1).div(e, prec + 16)).div(2, prec)
440
+ end
441
+ e = CALC.exp(x, w)
442
+ e.sub(CALC.one.div(e, w), w).div(2, prec)
443
+ end
444
+
445
+ def cosh(x, prec)
446
+ prec = CALC.precision(prec, :cosh)
447
+ x = CALC.coerce(x, prec, :cosh)
448
+ return CALC.nan_result if x.nan?
449
+ return CALC.infinity_result if x.infinite?
450
+ return BigDecimal(1) if x.zero?
451
+
452
+ w = prec + CALC::GUARD
453
+ return CALC.cosh_small(x, w).mult(1, prec) if x.abs < 1
454
+
455
+ if x.exponent >= 21
456
+ e = exp(x, prec + 16)
457
+ return (e + BigDecimal(1).div(e, prec + 16)).div(2, prec)
458
+ end
459
+ e = CALC.exp(x, w)
460
+ e.add(CALC.one.div(e, w), w).div(2, prec)
461
+ end
462
+
463
+ def tanh(x, prec)
464
+ prec = CALC.precision(prec, :tanh)
465
+ x = CALC.coerce(x, prec, :tanh)
466
+ return CALC.nan_result if x.nan?
467
+ return BigDecimal(x.infinite?) if x.infinite?
468
+ return BigDecimal(0) if x.zero?
469
+
470
+ w = prec + CALC::GUARD
471
+ return CALC.sinh_small(x, w).div(CALC.cosh_small(x, w), prec) if x.abs < 1
472
+
473
+ if x.exponent >= 21
474
+ e = exp(x, prec + 16)
475
+ inverse = BigDecimal(1).div(e, prec + 16)
476
+ return (e - inverse).div(e + inverse, prec)
477
+ end
478
+ # tanh |x| = (1 - t) / (1 + t) with t = e**(-2|x|).
479
+ t = CALC.exp(CALC.times(x.abs, BigDecimal(-2)), w)
480
+ value = CALC.one.sub(t, w).div(CALC.one.add(t, w), w)
481
+ (x.negative? ? -value : value).mult(1, prec)
482
+ end
483
+
484
+ def asinh(x, prec)
485
+ prec = CALC.precision(prec, :asinh)
486
+ x = CALC.coerce(x, prec, :asinh)
487
+ return CALC.nan_result if x.nan?
488
+ return CALC.infinity_result * x.infinite? if x.infinite?
489
+ return -asinh(-x, prec) if x.negative?
490
+ return BigDecimal(0) if x.zero?
491
+
492
+ w = prec + CALC::GUARD
493
+ root = CALC.sqrt(CALC.plus(CALC.times(x, x), CALC.one), w + [-x.exponent, 0].max)
494
+ CALC.log(CALC.plus(x, root), w).mult(1, prec)
495
+ end
496
+
497
+ def acosh(x, prec)
498
+ prec = CALC.precision(prec, :acosh)
499
+ x = CALC.coerce(x, prec, :acosh)
500
+ raise Math::DomainError, 'Out of domain argument for acosh' if x < 1
501
+ return CALC.infinity_result if x.infinite?
502
+ return CALC.nan_result if x.nan?
503
+ return BigDecimal(0) if x == 1
504
+
505
+ w = prec + CALC::GUARD
506
+ root = CALC.sqrt(CALC.minus(CALC.times(x, x), CALC.one), w)
507
+ CALC.log(CALC.plus(x, root), w).mult(1, prec)
508
+ end
509
+
510
+ def atanh(x, prec)
511
+ prec = CALC.precision(prec, :atanh)
512
+ x = CALC.coerce(x, prec, :atanh)
513
+ raise Math::DomainError, 'Out of domain argument for atanh' if x < -1 || x > 1
514
+ return CALC.nan_result if x.nan?
515
+ return CALC.infinity_result if x == 1
516
+ return -CALC.infinity_result if x == -1
517
+ return BigDecimal(0) if x.zero?
518
+
519
+ w = prec + CALC::GUARD
520
+ up = CALC.log(CALC.plus(CALC.one, x), w)
521
+ down = CALC.log(CALC.minus(CALC.one, x), w)
522
+ CALC.minus(up, down).div(2, prec)
523
+ end
524
+
525
+ def log2(x, prec) = CALC.log_base(x, prec, 2, :log2)
526
+ def log10(x, prec) = CALC.log_base(x, prec, 10, :log10)
527
+
528
+ def log1p(x, prec)
529
+ prec = CALC.precision(prec, :log1p)
530
+ x = CALC.coerce(x, prec, :log1p)
531
+ raise Math::DomainError, 'Out of domain argument for log1p' if x < -1
532
+
533
+ log(x + 1, prec)
534
+ end
535
+
536
+ def expm1(x, prec)
537
+ prec = CALC.precision(prec, :expm1)
538
+ x = CALC.coerce(x, prec, :expm1)
539
+ return BigDecimal(-1) if x.infinite? == -1
540
+
541
+ exp_prec = if x < -1
542
+ prec + (0.4342944819032518 * x).ceil + 16
543
+ elsif x < 1
544
+ prec - x.exponent + 16
545
+ else
546
+ prec
547
+ end
548
+ return BigDecimal(-1) if exp_prec <= 0
549
+
550
+ exp(x, exp_prec).sub(1, prec)
551
+ end
552
+
553
+ def erf(x, prec)
554
+ prec = CALC.precision(prec, :erf)
555
+ x = CALC.coerce(x, prec, :erf)
556
+ return CALC.nan_result if x.nan?
557
+ return BigDecimal(x.infinite?) if x.infinite?
558
+ return BigDecimal(0) if x.zero?
559
+ return -erf(-x, prec) if x.negative?
560
+ return BigDecimal(1) if x > 5_000_000_000
561
+
562
+ w = prec + CALC::GUARD
563
+ value = CALC.erf(x, w)
564
+ value ? value.mult(1, prec) : BigDecimal(1).sub(CALC.tiny(w), prec)
565
+ end
566
+
567
+ def erfc(x, prec)
568
+ prec = CALC.precision(prec, :erfc)
569
+ x = CALC.coerce(x, prec, :erfc)
570
+ return CALC.nan_result if x.nan?
571
+ return BigDecimal(1 - x.infinite?) if x.infinite?
572
+ return BigDecimal(1) if x.zero?
573
+
574
+ w = prec + CALC::GUARD
575
+ if x.negative?
576
+ value = CALC.erf(-x, w)
577
+ return value ? CALC.plus(CALC.one, value).mult(1, prec) : BigDecimal(2).sub(CALC.tiny(w), prec)
578
+ end
579
+ return CALC.underflow_result if x > 5_000_000_000
580
+
581
+ CALC.erfc(x, w).mult(1, prec)
582
+ end
583
+
584
+ def gamma(x, prec)
585
+ prec = CALC.precision(prec, :gamma)
586
+ x = CALC.coerce(x, prec, :gamma)
587
+ CALC.gamma_finite!(x)
588
+ w = prec + CALC::GUARD
589
+ if x < 0.5
590
+ raise Math::DomainError, 'Numerical argument is out of domain - gamma' if x.frac.zero?
591
+
592
+ # Euler's reflection formula: gamma(x) gamma(1 - x) = pi / sin(pi x).
593
+ sine = CALC.sin_pi(x, w)
594
+ rest = CALC.gamma_positive(CALC.minus(CALC.one, x), w)
595
+ return CALC.pi_decimal(w).div(sine.mult(rest, w), prec)
596
+ end
597
+ x = x.mult(1, prec + 16 + x.exponent + 10)
598
+ CALC.gamma_positive(x, w).mult(1, prec)
599
+ end
600
+
601
+ def lgamma(x, prec)
602
+ prec = CALC.precision(prec, :lgamma)
603
+ x = CALC.coerce(x, prec, :lgamma)
604
+ CALC.gamma_finite!(x)
605
+ w = prec + CALC::GUARD
606
+ if x < 0.5
607
+ return [BigDecimal::INFINITY, 1] if x.frac.zero?
608
+
609
+ value, sign = CALC.lgamma_reflected(x, w)
610
+ return [value.mult(1, prec), sign]
611
+ end
612
+ prec2 = prec + 16
613
+ near_one = x < 3 ? (x - 1).exponent : 0
614
+ near_two = x < 3 ? (x - 2).exponent : 0
615
+ if near_one < -prec2 || near_two < -prec2
616
+ # So close to 1 or 2 that ln gamma is linear there, as in bigdecimal 4.x.
617
+ base = near_one < -prec2 ? 1 : 2
618
+ d = BigDecimal(1)._decimal_shift(1 - prec2)
619
+ value, sign = lgamma(base + d, prec2)
620
+ return [value.mult(x - base, prec2).div(d, prec), sign]
621
+ end
622
+ extra = [-near_one, -near_two, 0].max
623
+ x = x.mult(1, prec2 + extra + x.exponent + 10)
624
+ if x.frac.zero? && x <= CALC::FACTORIAL_LIMIT
625
+ factorial = CALC.factorial(x.to_i - 1)
626
+ return [BigDecimal(0), 1] if factorial == 1
627
+
628
+ return [CALC.log(BigDecimal(factorial), w).mult(1, prec), 1]
629
+ end
630
+ digits = w + extra
631
+ loop do
632
+ value = CALC.lgamma_positive(x, digits)
633
+ return [value.mult(1, prec), 1] if value.nonzero? && digits + value.exponent >= w
634
+
635
+ digits += w - (digits + (value.zero? ? -digits : value.exponent)) + 5
636
+ end
637
+ end
638
+
639
+ def frexp(x)
640
+ x = CALC.coerce(x, 0, :frexp)
641
+ return [x, 0] unless x.finite?
642
+
643
+ exponent = x.exponent
644
+ [x._decimal_shift(-exponent), exponent]
645
+ end
646
+
647
+ def ldexp(x, exponent)
648
+ x = CALC.coerce(x, 0, :ldexp)
649
+ x.finite? ? x._decimal_shift(exponent) : x
650
+ end
651
+
652
+ def PI(prec) # rubocop:disable Naming/MethodName
653
+ prec = CALC.precision(prec, :PI)
654
+ CALC.pi_decimal(prec + 10).mult(1, prec)
655
+ end
656
+
657
+ def E(prec) # rubocop:disable Naming/MethodName
658
+ prec = CALC.precision(prec, :E)
659
+ exp(1, prec)
660
+ end
661
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ # bigdecimal/util of bigdecimal 4.x: to_d on the numbers, strings and nil.
4
+
5
+ class Integer
6
+ def to_d = BigDecimal(self)
7
+ end
8
+
9
+ class Float
10
+ def to_d(precision = 0) = BigDecimal(self, precision)
11
+ end
12
+
13
+ class String
14
+ def to_d = BigDecimal.interpret_loosely(self)
15
+ end
16
+
17
+ class BigDecimal
18
+ # Plain notation as bigdecimal/util writes it, which drops the sign of a
19
+ # value between -1 and 0.
20
+ def to_digits
21
+ return to_s if nan? || infinite? || zero?
22
+
23
+ whole = to_i.to_s
24
+ _, digits, _, exponent = frac.split
25
+ "#{whole}.#{'0' * -exponent}#{digits}"
26
+ end
27
+
28
+ def to_d = self
29
+ end
30
+
31
+ class Rational
32
+ def to_d(precision = 0) = BigDecimal(self, precision)
33
+ end
34
+
35
+ class Complex
36
+ def to_d(precision = 0)
37
+ BigDecimal(self) unless imaginary.zero? # raises the error for a non-zero imaginary part
38
+ BigDecimal(real, precision)
39
+ end
40
+ end
41
+
42
+ class NilClass
43
+ def to_d = BigDecimal(0)
44
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BigDecimalPure
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end