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,582 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BigDecimalPure
|
|
4
|
+
# The arithmetic behind BigMath, on Integers in fixed point: a real r is the
|
|
5
|
+
# Integer r * 10**s for a scale s. Each routine takes a BigDecimal and a
|
|
6
|
+
# number of significant digits w and returns a BigDecimal with at least w
|
|
7
|
+
# correct digits; BigMath asks for its precision plus a margin and rounds
|
|
8
|
+
# once, in the current rounding mode. The C version rounds after each step
|
|
9
|
+
# instead, so the two agree except when the exact value lies within about
|
|
10
|
+
# 10**-(prec+20) of a rounding boundary.
|
|
11
|
+
#
|
|
12
|
+
# Series with alternating terms keep the terms' magnitudes, because Integer
|
|
13
|
+
# division rounds a negative quotient down and a term would never reach 0.
|
|
14
|
+
module Calc # :nodoc: all
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
# Digits computed beyond the precision BigMath returns.
|
|
18
|
+
GUARD = 24
|
|
19
|
+
|
|
20
|
+
@constants = {}
|
|
21
|
+
@bernoulli = []
|
|
22
|
+
|
|
23
|
+
def pow10(n) = BigDecimal._pow10(n)
|
|
24
|
+
def ndigits(n) = BigDecimal._ndigits(n.abs)
|
|
25
|
+
|
|
26
|
+
# x * 10**s, truncated toward zero.
|
|
27
|
+
def fixed(x, s)
|
|
28
|
+
_, sign, coef, exp = x._parts
|
|
29
|
+
e = exp + s
|
|
30
|
+
v = e >= 0 ? coef * pow10(e) : coef / pow10(-e)
|
|
31
|
+
sign.negative? ? -v : v
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# m * 10**-s, exactly.
|
|
35
|
+
def decimal(m, s) = BigDecimal._make(m.negative? ? -1 : 1, m.abs, -s)
|
|
36
|
+
|
|
37
|
+
# Exact sums and products of finite values, untouched by BigDecimal.limit.
|
|
38
|
+
def plus(a, b)
|
|
39
|
+
_, sa, ca, ea = a._parts
|
|
40
|
+
_, sb, cb, eb = b._parts
|
|
41
|
+
e = [ea, eb].min
|
|
42
|
+
sum = (sa * ca * pow10(ea - e)) + (sb * cb * pow10(eb - e))
|
|
43
|
+
BigDecimal._make(sum.negative? ? -1 : 1, sum.abs, e)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def minus(a, b) = plus(a, -b)
|
|
47
|
+
|
|
48
|
+
def times(a, b)
|
|
49
|
+
_, sa, ca, ea = a._parts
|
|
50
|
+
_, sb, cb, eb = b._parts
|
|
51
|
+
BigDecimal._make(sa * sb, ca * cb, ea + eb)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def one = BigDecimal(1)
|
|
55
|
+
|
|
56
|
+
# ---- constants, cached at the largest scale asked for so far ----------
|
|
57
|
+
|
|
58
|
+
def constant(name, s)
|
|
59
|
+
digits, value = @constants[name]
|
|
60
|
+
if digits.nil? || digits < s
|
|
61
|
+
digits = [s, (digits || 0) + ((digits || 0) / 2)].max
|
|
62
|
+
guard = ndigits(digits) + 4
|
|
63
|
+
value = yield(digits + guard) / pow10(guard)
|
|
64
|
+
@constants[name] = [digits, value]
|
|
65
|
+
end
|
|
66
|
+
value / pow10(digits - s)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# atan(1/n) * 10**s: 1/n - 1/(3n^3) + 1/(5n^5) - ...
|
|
70
|
+
def atan_inv(n, s)
|
|
71
|
+
n2 = n * n
|
|
72
|
+
term = pow10(s) / n
|
|
73
|
+
sum = term
|
|
74
|
+
k = 1
|
|
75
|
+
until term.zero?
|
|
76
|
+
term /= n2
|
|
77
|
+
k += 2
|
|
78
|
+
sum += (k % 4 == 1 ? 1 : -1) * (term / k)
|
|
79
|
+
end
|
|
80
|
+
sum
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# atanh(1/n) * 10**s: 1/n + 1/(3n^3) + 1/(5n^5) + ...
|
|
84
|
+
def atanh_inv(n, s)
|
|
85
|
+
n2 = n * n
|
|
86
|
+
term = pow10(s) / n
|
|
87
|
+
sum = term
|
|
88
|
+
k = 1
|
|
89
|
+
until term.zero?
|
|
90
|
+
term /= n2
|
|
91
|
+
k += 2
|
|
92
|
+
sum += term / k
|
|
93
|
+
end
|
|
94
|
+
sum
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Machin's formula.
|
|
98
|
+
def pi(s) = constant(:pi, s) { |d| (16 * atan_inv(5, d)) - (4 * atan_inv(239, d)) }
|
|
99
|
+
|
|
100
|
+
# ln 2 = 18 atanh(1/26) - 2 atanh(1/4801) + 8 atanh(1/8749).
|
|
101
|
+
def ln2(s)
|
|
102
|
+
constant(:ln2, s) { |d| (18 * atanh_inv(26, d)) - (2 * atanh_inv(4801, d)) + (8 * atanh_inv(8749, d)) }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# ln 10 = 3 ln 2 + ln 1.25, and ln 1.25 = 2 atanh(1/9).
|
|
106
|
+
def ln10(s) = constant(:ln10, s) { |d| (3 * ln2(d)) + (2 * atanh_inv(9, d)) }
|
|
107
|
+
|
|
108
|
+
def pi_decimal(w) = decimal(pi(w + 2), w + 2)
|
|
109
|
+
|
|
110
|
+
# ---- exp and log ------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
# e**x for a finite x with |x| < 10**21, to w digits: e**x = 10**k * e**r
|
|
113
|
+
# with r = x - k ln 10 in [0, ln 10).
|
|
114
|
+
def exp(x, w)
|
|
115
|
+
return one if x.zero?
|
|
116
|
+
# For a tiny x, 1 + x + x**2/2 already has w digits, and a fixed-point
|
|
117
|
+
# computation at w digits would round the x away.
|
|
118
|
+
return plus(one, plus(x, times(times(x, x), BigDecimal('0.5')))) if x.exponent < -(w / 2) - 2
|
|
119
|
+
|
|
120
|
+
s0 = [x.exponent, 0].max + 10
|
|
121
|
+
k = fixed(x, s0).div(ln10(s0))
|
|
122
|
+
s = w + ndigits(k) + 12
|
|
123
|
+
r = fixed(x, s) - (k * ln10(s))
|
|
124
|
+
BigDecimal._make(1, exp_fixed(r, s), k - s)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# e**(r / 10**s) * 10**s for |r / 10**s| below a few units: halve the
|
|
128
|
+
# argument j times, sum the Taylor series, square j times.
|
|
129
|
+
def exp_fixed(r, s)
|
|
130
|
+
return pow10(2 * s) / exp_fixed(-r, s) if r.negative?
|
|
131
|
+
|
|
132
|
+
halvings = [Integer.sqrt(3 * s), 4].max
|
|
133
|
+
s2 = s + ndigits(s) + (halvings / 3) + 4
|
|
134
|
+
unit = pow10(s2)
|
|
135
|
+
x = (r * pow10(s2 - s)) >> halvings
|
|
136
|
+
sum = unit
|
|
137
|
+
term = unit
|
|
138
|
+
n = 0
|
|
139
|
+
loop do
|
|
140
|
+
n += 1
|
|
141
|
+
term = term * x / (unit * n)
|
|
142
|
+
break if term.zero?
|
|
143
|
+
|
|
144
|
+
sum += term
|
|
145
|
+
end
|
|
146
|
+
halvings.times { sum = sum * sum / unit }
|
|
147
|
+
sum / pow10(s2 - s)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# ln x for a finite x > 0, x != 1, to w digits, by
|
|
151
|
+
# ln t = 2 atanh((t - 1) / (t + 1)) on t near 1.
|
|
152
|
+
def log(x, w)
|
|
153
|
+
return BigDecimal(0) if x == 1
|
|
154
|
+
|
|
155
|
+
if x > BigDecimal('0.5') && x < BigDecimal(2)
|
|
156
|
+
# |ln x| can be as small as |x - 1|: scale for that many digits.
|
|
157
|
+
xr = x.to_r
|
|
158
|
+
d = xr - 1
|
|
159
|
+
magnitude = ndigits(d.numerator) - ndigits(d.denominator)
|
|
160
|
+
s = w + ndigits(w) + 10 + [-magnitude, 0].max
|
|
161
|
+
z = (d * pow10(s) / (xr + 1)).round
|
|
162
|
+
return decimal(2 * atanh_fixed(z, s), s)
|
|
163
|
+
end
|
|
164
|
+
# x = m * 10**e with m in [1, 10), m = t * 2**k with t near 1;
|
|
165
|
+
# |ln x| >= ln 2 here, so a fixed scale gives w digits.
|
|
166
|
+
e = x.exponent - 1
|
|
167
|
+
m = x._decimal_shift(-e)
|
|
168
|
+
k = Math.log2(m.to_f).round
|
|
169
|
+
t = m.to_r / (1 << k)
|
|
170
|
+
s = w + ndigits(w) + ndigits(e.abs + k + 1) + 10
|
|
171
|
+
z = ((t - 1) * pow10(s) / (t + 1)).round
|
|
172
|
+
decimal((2 * atanh_fixed(z, s)) + (k * ln2(s)) + (e * ln10(s)), s)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# atanh(z / 10**s) * 10**s for |z / 10**s| <= 1/3.
|
|
176
|
+
def atanh_fixed(z, s)
|
|
177
|
+
return -atanh_fixed(-z, s) if z.negative?
|
|
178
|
+
|
|
179
|
+
unit = pow10(s)
|
|
180
|
+
z2 = z * z / unit
|
|
181
|
+
term = z
|
|
182
|
+
sum = z
|
|
183
|
+
k = 1
|
|
184
|
+
loop do
|
|
185
|
+
term = term * z2 / unit
|
|
186
|
+
break if term.zero?
|
|
187
|
+
|
|
188
|
+
k += 2
|
|
189
|
+
sum += term / k
|
|
190
|
+
end
|
|
191
|
+
sum
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# ---- roots, exactly ---------------------------------------------------
|
|
195
|
+
|
|
196
|
+
# [root, exponent, exact]: root * 10**exponent is sqrt(x) rounded down
|
|
197
|
+
# to at least w digits, exact when it is the whole square root.
|
|
198
|
+
def sqrt_digits(x, w)
|
|
199
|
+
_, _, coef, exp = x._parts
|
|
200
|
+
shift = [(2 * w) + 2 - ndigits(coef), 0].max
|
|
201
|
+
shift += 1 if (exp - shift).odd?
|
|
202
|
+
n = coef * pow10(shift)
|
|
203
|
+
root = Integer.sqrt(n)
|
|
204
|
+
[root, (exp - shift) / 2, root * root == n]
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def cbrt_digits(x, w)
|
|
208
|
+
_, _, coef, exp = x._parts
|
|
209
|
+
shift = [(3 * w) + 3 - ndigits(coef), 0].max
|
|
210
|
+
shift += 1 until ((exp - shift) % 3).zero?
|
|
211
|
+
n = coef * pow10(shift)
|
|
212
|
+
root = icbrt(n)
|
|
213
|
+
[root, (exp - shift) / 3, root * root * root == n]
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def icbrt(n)
|
|
217
|
+
return n if n < 2
|
|
218
|
+
|
|
219
|
+
x = 1 << ((n.bit_length + 2) / 3)
|
|
220
|
+
loop do
|
|
221
|
+
y = ((2 * x) + (n / (x * x))) / 3
|
|
222
|
+
return x if y >= x
|
|
223
|
+
|
|
224
|
+
x = y
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def sqrt(x, w)
|
|
229
|
+
root, exp, = sqrt_digits(x, w)
|
|
230
|
+
BigDecimal._make(1, root, exp)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# ---- trigonometry -----------------------------------------------------
|
|
234
|
+
|
|
235
|
+
# [q, r2, s]: x = q * pi/2 + r with r2 = 2r * 10**s, |r| <= pi/4, and r2
|
|
236
|
+
# good to w + 10 digits. The scale grows until the cancellation in
|
|
237
|
+
# x - q pi/2 leaves enough digits.
|
|
238
|
+
def reduce_half_pi(x, w)
|
|
239
|
+
e = [x.exponent, 0].max
|
|
240
|
+
extra = 0
|
|
241
|
+
loop do
|
|
242
|
+
s = w + e + extra + ndigits(w) + 10
|
|
243
|
+
p = pi(s)
|
|
244
|
+
xs = fixed(x, s)
|
|
245
|
+
q = ((4 * xs) + p).div(2 * p)
|
|
246
|
+
r2 = (2 * xs) - (q * p)
|
|
247
|
+
deficit = (w + 10) - (ndigits(r2) - ndigits(q) - 2)
|
|
248
|
+
deficit = w + 10 + s if r2.zero?
|
|
249
|
+
return [q, r2, s] if deficit <= 0
|
|
250
|
+
|
|
251
|
+
extra += deficit + 5
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# sin or cos of a finite nonzero x, to w digits.
|
|
256
|
+
def sin_cos(x, w, cosine)
|
|
257
|
+
q, r2, s = reduce_half_pi(x, w)
|
|
258
|
+
n = q % 4
|
|
259
|
+
use_cos = cosine ? n.even? : n.odd?
|
|
260
|
+
negative = cosine ? [1, 2].include?(n) : n >= 2
|
|
261
|
+
value, scale = sin_or_cos(r2, s, w, use_cos)
|
|
262
|
+
decimal(negative ? -value : value, scale)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# [value, scale]: sin(r) or cos(r) * 10**scale for r2 = 2r * 10**s.
|
|
266
|
+
def sin_or_cos(r2, s, w, use_cos)
|
|
267
|
+
size = ndigits(r2) - s # |r| is about 10**size
|
|
268
|
+
# sin(r) is about r, cos(r) - 1 about r**2/2: keep those digits too.
|
|
269
|
+
t = w + ndigits(w) + 10 + (use_cos ? [-2 * size, 0].max : [-size, 0].max)
|
|
270
|
+
a = r2.abs * pow10(t) / (2 * pow10(s))
|
|
271
|
+
return [cos_fixed(a, t), t] if use_cos
|
|
272
|
+
|
|
273
|
+
[r2.negative? ? -sin_fixed(a, t) : sin_fixed(a, t), t]
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def sin_fixed(a, t)
|
|
277
|
+
unit = pow10(t)
|
|
278
|
+
a2 = a * a / unit
|
|
279
|
+
term = a
|
|
280
|
+
sum = a
|
|
281
|
+
k = 1
|
|
282
|
+
loop do
|
|
283
|
+
term = term * a2 / (unit * (k + 1) * (k + 2))
|
|
284
|
+
break if term.zero?
|
|
285
|
+
|
|
286
|
+
k += 2
|
|
287
|
+
sum += k % 4 == 1 ? term : -term
|
|
288
|
+
end
|
|
289
|
+
sum
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def cos_fixed(a, t)
|
|
293
|
+
unit = pow10(t)
|
|
294
|
+
a2 = a * a / unit
|
|
295
|
+
term = unit
|
|
296
|
+
sum = unit
|
|
297
|
+
k = 0
|
|
298
|
+
loop do
|
|
299
|
+
term = term * a2 / (unit * (k + 1) * (k + 2))
|
|
300
|
+
break if term.zero?
|
|
301
|
+
|
|
302
|
+
k += 2
|
|
303
|
+
sum += k % 4 == 2 ? -term : term
|
|
304
|
+
end
|
|
305
|
+
sum
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# tan of a finite nonzero x, to w digits.
|
|
309
|
+
def tan(x, w)
|
|
310
|
+
q, r2, s = reduce_half_pi(x, w + 5)
|
|
311
|
+
sin_r, sin_scale = sin_or_cos(r2, s, w + 5, false)
|
|
312
|
+
cos_r, cos_scale = sin_or_cos(r2, s, w + 5, true)
|
|
313
|
+
sine = decimal(sin_r, sin_scale)
|
|
314
|
+
cosine = decimal(cos_r, cos_scale)
|
|
315
|
+
# tan(r + q pi/2) is tan r for even q and -cot r for odd q.
|
|
316
|
+
q.even? ? sine.div(cosine, w + 5) : -cosine.div(sine, w + 5)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# atan of a finite x > 0, to w digits.
|
|
320
|
+
def atan(x, w)
|
|
321
|
+
s = w + ndigits(w) + 10
|
|
322
|
+
if x > one
|
|
323
|
+
# pi/2 - atan(1/x), which is at least pi/4.
|
|
324
|
+
inverse = pow10(2 * s) / fixed(x, s)
|
|
325
|
+
return decimal((pi(s) / 2) - atan_fixed(inverse, s), s)
|
|
326
|
+
end
|
|
327
|
+
s += [-x.exponent, 0].max
|
|
328
|
+
decimal(atan_fixed(fixed(x, s), s), s)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# atan(a / 10**s) * 10**s for 0 <= a / 10**s <= 1: halve the angle with
|
|
332
|
+
# atan y = 2 atan(y / (1 + sqrt(1 + y**2))) until y < 0.01, then Taylor.
|
|
333
|
+
def atan_fixed(a, s)
|
|
334
|
+
guard = ndigits(s) + 4
|
|
335
|
+
s2 = s + guard
|
|
336
|
+
unit = pow10(s2)
|
|
337
|
+
y = a * pow10(guard)
|
|
338
|
+
halvings = 0
|
|
339
|
+
while y > unit / 100
|
|
340
|
+
y = y * unit / (unit + Integer.sqrt((unit * unit) + (y * y)))
|
|
341
|
+
halvings += 1
|
|
342
|
+
end
|
|
343
|
+
y2 = y * y / unit
|
|
344
|
+
term = y
|
|
345
|
+
sum = y
|
|
346
|
+
k = 1
|
|
347
|
+
loop do
|
|
348
|
+
term = term * y2 / unit
|
|
349
|
+
break if term.zero?
|
|
350
|
+
|
|
351
|
+
k += 2
|
|
352
|
+
sum += (k % 4 == 1 ? 1 : -1) * (term / k)
|
|
353
|
+
end
|
|
354
|
+
(sum << halvings) / pow10(guard)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# ---- hyperbolic functions for |x| < 1, by their series ----------------
|
|
358
|
+
|
|
359
|
+
# sinh(x) for finite 0 < |x| < 1, to w digits.
|
|
360
|
+
def sinh_small(x, w)
|
|
361
|
+
t = w + ndigits(w) + 10 + [-x.exponent, 0].max
|
|
362
|
+
a = fixed(x.abs, t)
|
|
363
|
+
unit = pow10(t)
|
|
364
|
+
a2 = a * a / unit
|
|
365
|
+
term = a
|
|
366
|
+
sum = a
|
|
367
|
+
k = 1
|
|
368
|
+
loop do
|
|
369
|
+
term = term * a2 / (unit * (k + 1) * (k + 2))
|
|
370
|
+
break if term.zero?
|
|
371
|
+
|
|
372
|
+
k += 2
|
|
373
|
+
sum += term
|
|
374
|
+
end
|
|
375
|
+
decimal(x.negative? ? -sum : sum, t)
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# cosh(x) for finite |x| < 1, to w digits.
|
|
379
|
+
def cosh_small(x, w)
|
|
380
|
+
t = w + ndigits(w) + 10 + [-2 * x.exponent, 0].max
|
|
381
|
+
a = fixed(x.abs, t)
|
|
382
|
+
unit = pow10(t)
|
|
383
|
+
a2 = a * a / unit
|
|
384
|
+
term = unit
|
|
385
|
+
sum = unit
|
|
386
|
+
k = 0
|
|
387
|
+
loop do
|
|
388
|
+
term = term * a2 / (unit * (k + 1) * (k + 2))
|
|
389
|
+
break if term.zero?
|
|
390
|
+
|
|
391
|
+
k += 2
|
|
392
|
+
sum += term
|
|
393
|
+
end
|
|
394
|
+
decimal(sum, t)
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# ---- the error function -----------------------------------------------
|
|
398
|
+
|
|
399
|
+
# erf(x) * 10**digits for a finite x > 0, with an absolute error of a few
|
|
400
|
+
# units, from the series with only positive terms
|
|
401
|
+
# erf(x) = 2/sqrt(pi) * exp(-x**2) * sum_n 2**n x**(2n+1) / (1*3*...*(2n+1)).
|
|
402
|
+
def erf_fixed(x, digits)
|
|
403
|
+
grow = ((x.to_f**2) / Math.log(10)).ceil # the sum reaches about 10**grow
|
|
404
|
+
s = digits + grow + ndigits(digits + grow) + 10 + [-x.exponent, 0].max
|
|
405
|
+
unit = pow10(s)
|
|
406
|
+
xs = fixed(x, s)
|
|
407
|
+
x2 = 2 * xs * xs / unit
|
|
408
|
+
term = xs
|
|
409
|
+
sum = xs
|
|
410
|
+
n = 0
|
|
411
|
+
loop do
|
|
412
|
+
n += 1
|
|
413
|
+
term = term * x2 / (unit * ((2 * n) + 1))
|
|
414
|
+
break if term.zero?
|
|
415
|
+
|
|
416
|
+
sum += term
|
|
417
|
+
end
|
|
418
|
+
w = digits + ndigits(digits) + 10
|
|
419
|
+
factor = exp(-times(x, x), w).mult(2, w).div(sqrt(pi_decimal(w + 2), w + 2), w)
|
|
420
|
+
fixed(decimal(sum, s).mult(factor, w + grow + 10), digits)
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
# erf(x) for a finite x > 0, to w digits, and nil when erf(x) is 1 to
|
|
424
|
+
# more than w digits.
|
|
425
|
+
def erf(x, w)
|
|
426
|
+
return nil if x.to_f**2 > (w + 5) * Math.log(10)
|
|
427
|
+
|
|
428
|
+
digits = w + 5 + [-x.exponent, 0].max
|
|
429
|
+
decimal(erf_fixed(x, digits), digits)
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# erfc(x) for a finite x > 0, to w digits.
|
|
433
|
+
def erfc(x, w)
|
|
434
|
+
xf = x.to_f
|
|
435
|
+
if xf < 0.5
|
|
436
|
+
# 1 - erf(x) is about 1 - 1.13 x: keep x's digits as well.
|
|
437
|
+
digits = w + 5 + [-x.exponent, 0].max
|
|
438
|
+
return decimal(pow10(digits) - erf_fixed(x, digits), digits)
|
|
439
|
+
end
|
|
440
|
+
return erfc_asymptotic(x, w) if xf * xf > (w + 12) * Math.log(10)
|
|
441
|
+
|
|
442
|
+
# 1 - erf(x) loses about log10(erfc(x)) digits to cancellation.
|
|
443
|
+
lost = ((xf * xf) + Math.log(xf * Math.sqrt(Math::PI))) / Math.log(10)
|
|
444
|
+
digits = w + 10 + lost.ceil
|
|
445
|
+
decimal(pow10(digits) - erf_fixed(x, digits), digits)
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# erfc(x) = exp(-x**2) / (x sqrt(pi)) * sum_n (-1)**n (2n-1)!! / (2x**2)**n
|
|
449
|
+
# for x**2 large enough that the smallest term is below 10**-(w+12).
|
|
450
|
+
def erfc_asymptotic(x, w)
|
|
451
|
+
s = w + ndigits(w) + 12
|
|
452
|
+
unit = pow10(s)
|
|
453
|
+
x2 = fixed(times(times(x, x), BigDecimal(2)), s)
|
|
454
|
+
term = unit
|
|
455
|
+
sum = unit
|
|
456
|
+
n = 0
|
|
457
|
+
loop do
|
|
458
|
+
n += 1
|
|
459
|
+
smaller = term * ((2 * n) - 1) * unit / x2
|
|
460
|
+
break if smaller.zero? || smaller >= term
|
|
461
|
+
|
|
462
|
+
term = smaller
|
|
463
|
+
sum += n.odd? ? -term : term
|
|
464
|
+
end
|
|
465
|
+
w2 = w + 10
|
|
466
|
+
root_pi = sqrt(pi_decimal(w2 + 2), w2 + 2)
|
|
467
|
+
exp(-times(x, x), w2).mult(decimal(sum, s), w2).div(x.mult(root_pi, w2), w2)
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
# ---- gamma --------------------------------------------------------------
|
|
471
|
+
|
|
472
|
+
# B_2, B_4, ..., B_2n as Rationals, from the tangent numbers
|
|
473
|
+
# (Brent and Harvey): B_2k = (-1)**(k-1) * 2k * T_k / (4**k * (4**k - 1)).
|
|
474
|
+
def bernoulli(count)
|
|
475
|
+
return @bernoulli if @bernoulli.size >= count
|
|
476
|
+
|
|
477
|
+
n = [count, (@bernoulli.size * 3 / 2) + 8].max
|
|
478
|
+
t = Array.new(n + 1, 0)
|
|
479
|
+
t[1] = 1
|
|
480
|
+
(2..n).each { |k| t[k] = (k - 1) * t[k - 1] }
|
|
481
|
+
(2..n).each do |k| # rubocop:disable Style/CombinableLoops -- the first loop must finish first
|
|
482
|
+
(k..n).each { |j| t[j] = ((j - k) * t[j - 1]) + ((j - k + 2) * t[j]) }
|
|
483
|
+
end
|
|
484
|
+
@bernoulli = (1..n).map do |k|
|
|
485
|
+
four = 1 << (2 * k)
|
|
486
|
+
value = Rational(2 * k * t[k], four * (four - 1))
|
|
487
|
+
k.odd? ? value : -value
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
# ln(2 pi) / 2.
|
|
492
|
+
def half_ln_2pi(s)
|
|
493
|
+
constant(:half_ln_2pi, s) do |d|
|
|
494
|
+
fixed(log(times(pi_decimal(d + 5), BigDecimal(2)), d + 5), d) / 2
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
# ln Gamma(z) for z >= 2*digits + 10 by Stirling's series, with an
|
|
499
|
+
# absolute error below 10**-digits:
|
|
500
|
+
# (z - 1/2) ln z - z + ln(2 pi)/2 + sum_k B_2k / (2k (2k-1) z**(2k-1)).
|
|
501
|
+
def lgamma_stirling(z, digits)
|
|
502
|
+
s = digits + ndigits(digits) + 10
|
|
503
|
+
log_z = log(z, s + [z.exponent, 0].max + ndigits(z.exponent.abs + 2) + 5)
|
|
504
|
+
main = fixed(times(minus(z, BigDecimal('0.5')), log_z), s) - fixed(z, s) + half_ln_2pi(s)
|
|
505
|
+
# z**-(2k-1) falls below 10**-s long before B_2k z**-(2k-1) does:
|
|
506
|
+
# carry the powers with as many more digits as the largest B_2k has.
|
|
507
|
+
extra = bernoulli_digits(stirling_terms(z, s)) + 5
|
|
508
|
+
scale = s + extra
|
|
509
|
+
unit = pow10(scale)
|
|
510
|
+
inverse = unit * unit / fixed(z, scale)
|
|
511
|
+
inverse2 = inverse * inverse / unit
|
|
512
|
+
power = inverse
|
|
513
|
+
sum = 0
|
|
514
|
+
k = 0
|
|
515
|
+
loop do
|
|
516
|
+
k += 1
|
|
517
|
+
b = bernoulli(k)[k - 1]
|
|
518
|
+
term = (b.numerator.abs * power) / (b.denominator * 2 * k * ((2 * k) - 1) * pow10(extra))
|
|
519
|
+
break if term.zero?
|
|
520
|
+
|
|
521
|
+
sum += b.negative? ? -term : term
|
|
522
|
+
power = power * inverse2 / unit
|
|
523
|
+
end
|
|
524
|
+
decimal(main + sum, s)
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
# How many Stirling terms bring |B_2k| / (2k (2k-1) z**(2k-1)) below
|
|
528
|
+
# 10**-s, with |B_2k| about 2 (2k)! / (2 pi)**2k; a few more for safety.
|
|
529
|
+
def stirling_terms(z, s)
|
|
530
|
+
log_z = z.to_f.finite? ? Math.log(z.to_f) : (z.exponent * Math.log(10))
|
|
531
|
+
limit = -(s + 5) * Math.log(10)
|
|
532
|
+
k = 1
|
|
533
|
+
k += 1 while k < 100_000 && log_bernoulli(k) - Math.log(2.0 * k * ((2 * k) - 1)) - (((2 * k) - 1) * log_z) > limit
|
|
534
|
+
k + 10
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
# ln |B_2k|, near enough.
|
|
538
|
+
def log_bernoulli(k) = Math.log(2) + Math.lgamma((2 * k) + 1)[0] - (2 * k * Math.log(2 * Math::PI))
|
|
539
|
+
|
|
540
|
+
def bernoulli_digits(k) = [(log_bernoulli(k) / Math.log(10)).ceil, 0].max
|
|
541
|
+
|
|
542
|
+
# ln Gamma(x) for a finite x >= 0.5, with an absolute error below
|
|
543
|
+
# 10**-digits: Stirling at z = x + m, less ln(x (x+1) ... (x+m-1)).
|
|
544
|
+
def lgamma_positive(x, digits)
|
|
545
|
+
start = (2 * digits) + 10
|
|
546
|
+
m = [start - x.floor, 0].max
|
|
547
|
+
return lgamma_stirling(x, digits) if m.zero?
|
|
548
|
+
|
|
549
|
+
z = plus(x, BigDecimal(m))
|
|
550
|
+
stirling = lgamma_stirling(z, digits + 2)
|
|
551
|
+
size = ndigits(((m + 1) * (Math.log([z.to_f, 2].max) + 1)).ceil)
|
|
552
|
+
w = digits + size + ndigits(m) + 10
|
|
553
|
+
product = x
|
|
554
|
+
(1...m).each { |i| product = product.mult(plus(x, BigDecimal(i)), w) }
|
|
555
|
+
s = digits + 5
|
|
556
|
+
decimal(fixed(stirling, s) - fixed(log(product, w), s), s)
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
# sin(pi x) for a finite x that is not an integer, to w digits, by
|
|
560
|
+
# reducing x to [-1/2, 1/2] exactly first.
|
|
561
|
+
def sin_pi(x, w)
|
|
562
|
+
n = x.round
|
|
563
|
+
f = minus(x, BigDecimal(n))
|
|
564
|
+
value = sin_cos(times(pi_decimal(w + 10 - [f.exponent, 0].min), f), w + 5, false)
|
|
565
|
+
n.odd? ? -value : value
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
def factorial(n)
|
|
569
|
+
return 1 if n < 2
|
|
570
|
+
|
|
571
|
+
product_range(2, n)
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
def product_range(low, high)
|
|
575
|
+
return low if low == high
|
|
576
|
+
return low * high if high == low + 1
|
|
577
|
+
|
|
578
|
+
mid = (low + high) / 2
|
|
579
|
+
product_range(low, mid) * product_range(mid + 1, high)
|
|
580
|
+
end
|
|
581
|
+
end
|
|
582
|
+
end
|