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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/README.md +64 -14
- data/lib/bigdecimal/pure.rb +9 -319
- data/lib/bigdecimal_pure/calc.rb +582 -0
- data/lib/bigdecimal_pure/decimal.rb +1479 -0
- data/lib/bigdecimal_pure/math.rb +661 -0
- data/lib/bigdecimal_pure/util.rb +44 -0
- data/lib/bigdecimal_pure/version.rb +1 -1
- metadata +8 -3
|
@@ -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
|