bigdecimal 3.2.1 → 3.3.1

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.
data/lib/bigdecimal.rb CHANGED
@@ -1,5 +1,356 @@
1
1
  if RUBY_ENGINE == 'jruby'
2
2
  JRuby::Util.load_ext("org.jruby.ext.bigdecimal.BigDecimalLibrary")
3
+
4
+ class BigDecimal
5
+ def _decimal_shift(i) # :nodoc:
6
+ to_java.move_point_right(i).to_d
7
+ end
8
+ end
3
9
  else
4
10
  require 'bigdecimal.so'
5
11
  end
12
+
13
+ class BigDecimal
14
+ module Internal # :nodoc:
15
+
16
+ # Coerce x to BigDecimal with the specified precision.
17
+ # TODO: some methods (example: BigMath.exp) require more precision than specified to coerce.
18
+ def self.coerce_to_bigdecimal(x, prec, method_name) # :nodoc:
19
+ case x
20
+ when BigDecimal
21
+ return x
22
+ when Integer, Float
23
+ return BigDecimal(x, 0)
24
+ when Rational
25
+ return BigDecimal(x, [prec, 2 * BigDecimal.double_fig].max)
26
+ end
27
+ raise ArgumentError, "#{x.inspect} can't be coerced into BigDecimal"
28
+ end
29
+
30
+ def self.coerce_validate_prec(prec, method_name, accept_zero: false) # :nodoc:
31
+ unless Integer === prec
32
+ original = prec
33
+ # Emulate Integer.try_convert for ruby < 3.1
34
+ if prec.respond_to?(:to_int)
35
+ prec = prec.to_int
36
+ else
37
+ raise TypeError, "no implicit conversion of #{original.class} into Integer"
38
+ end
39
+ raise TypeError, "can't convert #{original.class} to Integer" unless Integer === prec
40
+ end
41
+
42
+ if accept_zero
43
+ raise ArgumentError, "Negative precision for #{method_name}" if prec < 0
44
+ else
45
+ raise ArgumentError, "Zero or negative precision for #{method_name}" if prec <= 0
46
+ end
47
+ prec
48
+ end
49
+
50
+ def self.infinity_computation_result # :nodoc:
51
+ if BigDecimal.mode(BigDecimal::EXCEPTION_ALL).anybits?(BigDecimal::EXCEPTION_INFINITY)
52
+ raise FloatDomainError, "Computation results in 'Infinity'"
53
+ end
54
+ BigDecimal::INFINITY
55
+ end
56
+
57
+ def self.nan_computation_result # :nodoc:
58
+ if BigDecimal.mode(BigDecimal::EXCEPTION_ALL).anybits?(BigDecimal::EXCEPTION_NaN)
59
+ raise FloatDomainError, "Computation results to 'NaN'"
60
+ end
61
+ BigDecimal::NAN
62
+ end
63
+ end
64
+
65
+ # call-seq:
66
+ # self ** other -> bigdecimal
67
+ #
68
+ # Returns the \BigDecimal value of +self+ raised to power +other+:
69
+ #
70
+ # b = BigDecimal('3.14')
71
+ # b ** 2 # => 0.98596e1
72
+ # b ** 2.0 # => 0.98596e1
73
+ # b ** Rational(2, 1) # => 0.98596e1
74
+ #
75
+ # Related: BigDecimal#power.
76
+ #
77
+ def **(y)
78
+ case y
79
+ when BigDecimal, Integer, Float, Rational
80
+ power(y)
81
+ when nil
82
+ raise TypeError, 'wrong argument type NilClass'
83
+ else
84
+ x, y = y.coerce(self)
85
+ x**y
86
+ end
87
+ end
88
+
89
+ # call-seq:
90
+ # power(n)
91
+ # power(n, prec)
92
+ #
93
+ # Returns the value raised to the power of n.
94
+ #
95
+ # Also available as the operator **.
96
+ #
97
+ def power(y, prec = 0)
98
+ prec = Internal.coerce_validate_prec(prec, :power, accept_zero: true)
99
+ x = self
100
+ y = Internal.coerce_to_bigdecimal(y, prec.nonzero? || n_significant_digits, :power)
101
+
102
+ return Internal.nan_computation_result if x.nan? || y.nan?
103
+ return BigDecimal(1) if y.zero?
104
+
105
+ if y.infinite?
106
+ if x < 0
107
+ return BigDecimal(0) if x < -1 && y.negative?
108
+ return BigDecimal(0) if x > -1 && y.positive?
109
+ raise Math::DomainError, 'Result undefined for negative base raised to infinite power'
110
+ elsif x < 1
111
+ return y.positive? ? BigDecimal(0) : BigDecimal::Internal.infinity_computation_result
112
+ elsif x == 1
113
+ return BigDecimal(1)
114
+ else
115
+ return y.positive? ? BigDecimal::Internal.infinity_computation_result : BigDecimal(0)
116
+ end
117
+ end
118
+
119
+ if x.infinite? && y < 0
120
+ # Computation result will be +0 or -0. Avoid overflow.
121
+ neg = x < 0 && y.frac.zero? && y % 2 == 1
122
+ return neg ? -BigDecimal(0) : BigDecimal(0)
123
+ end
124
+
125
+ if x.zero?
126
+ return BigDecimal(1) if y.zero?
127
+ return BigDecimal(0) if y > 0
128
+ if y.frac.zero? && y % 2 == 1 && x.sign == -1
129
+ return -BigDecimal::Internal.infinity_computation_result
130
+ else
131
+ return BigDecimal::Internal.infinity_computation_result
132
+ end
133
+ elsif x < 0
134
+ if y.frac.zero?
135
+ if y % 2 == 0
136
+ return (-x).power(y, prec)
137
+ else
138
+ return -(-x).power(y, prec)
139
+ end
140
+ else
141
+ raise Math::DomainError, 'Computation results in complex number'
142
+ end
143
+ elsif x == 1
144
+ return BigDecimal(1)
145
+ end
146
+
147
+ prec = BigDecimal.limit if prec.zero?
148
+ frac_part = y.frac
149
+
150
+ if frac_part.zero? && prec.zero?
151
+ # Infinite precision calculation for `x ** int` and `x.power(int)`
152
+ int_part = y.fix.to_i
153
+ int_part = -int_part if (neg = int_part < 0)
154
+ ans = BigDecimal(1)
155
+ n = 1
156
+ xn = x
157
+ while true
158
+ ans *= xn if int_part.allbits?(n)
159
+ n <<= 1
160
+ break if n > int_part
161
+ xn *= xn
162
+ # Detect overflow/underflow before consuming infinite memory
163
+ if (xn.exponent.abs - 1) * int_part / n >= 0x7FFFFFFFFFFFFFFF
164
+ return ((xn.exponent > 0) ^ neg ? BigDecimal::Internal.infinity_computation_result : BigDecimal(0)) * (int_part.even? || x > 0 ? 1 : -1)
165
+ end
166
+ end
167
+ return neg ? BigDecimal(1) / ans : ans
168
+ end
169
+
170
+ prec = [x.n_significant_digits, y.n_significant_digits, BigDecimal.double_fig].max + BigDecimal.double_fig if prec.zero?
171
+
172
+ if y < 0
173
+ inv = x.power(-y, prec)
174
+ return BigDecimal(0) if inv.infinite?
175
+ return BigDecimal::Internal.infinity_computation_result if inv.zero?
176
+ return BigDecimal(1).div(inv, prec)
177
+ end
178
+
179
+ prec2 = prec + BigDecimal.double_fig
180
+
181
+ if frac_part.zero? && y.exponent < Math.log(prec) * 5 + 20
182
+ # Use exponentiation by squaring if y is an integer and not too large
183
+ pow_prec = prec2 + y.exponent
184
+ n = 1
185
+ xn = x
186
+ ans = BigDecimal(1)
187
+ int_part = y.fix.to_i
188
+ while true
189
+ ans = ans.mult(xn, pow_prec) if int_part.allbits?(n)
190
+ n <<= 1
191
+ break if n > int_part
192
+ xn = xn.mult(xn, pow_prec)
193
+ end
194
+ ans.mult(1, prec)
195
+ else
196
+ if x > 1
197
+ # To calculate exp(z, prec), z needs prec+max(z.exponent, 0) precision if z > 0.
198
+ # Estimate (y*log(x)).exponent
199
+ logx_exponent = x < 2 ? (x - 1).exponent : Math.log10(x.exponent).round
200
+ ylogx_exponent = y.exponent + logx_exponent
201
+ prec2 += [ylogx_exponent, 0].max
202
+ end
203
+ BigMath.exp(BigMath.log(x, prec2).mult(y, prec2), prec)
204
+ end
205
+ end
206
+
207
+ # Returns the square root of the value.
208
+ #
209
+ # Result has at least prec significant digits.
210
+ #
211
+ def sqrt(prec)
212
+ prec = Internal.coerce_validate_prec(prec, :sqrt, accept_zero: true)
213
+ return Internal.infinity_computation_result if infinite? == 1
214
+
215
+ raise FloatDomainError, 'sqrt of negative value' if self < 0
216
+ raise FloatDomainError, "sqrt of 'NaN'(Not a Number)" if nan?
217
+ return self if zero?
218
+
219
+ if prec == 0
220
+ prec = BigDecimal.limit.nonzero? || n_significant_digits + BigDecimal.double_fig
221
+ end
222
+
223
+ ex = exponent / 2
224
+ x = _decimal_shift(-2 * ex)
225
+ y = BigDecimal(Math.sqrt(x.to_f), 0)
226
+ precs = [prec + BigDecimal.double_fig]
227
+ precs << 2 + precs.last / 2 while precs.last > BigDecimal.double_fig
228
+ precs.reverse_each do |p|
229
+ y = y.add(x.div(y, p), p).div(2, p)
230
+ end
231
+ y._decimal_shift(ex).mult(1, prec)
232
+ end
233
+ end
234
+
235
+ # Core BigMath methods for BigDecimal (log, exp) are defined here.
236
+ # Other methods (sin, cos, atan) are defined in 'bigdecimal/math.rb'.
237
+ module BigMath
238
+
239
+ # call-seq:
240
+ # BigMath.log(decimal, numeric) -> BigDecimal
241
+ #
242
+ # Computes the natural logarithm of +decimal+ to the specified number of
243
+ # digits of precision, +numeric+.
244
+ #
245
+ # If +decimal+ is zero or negative, raises Math::DomainError.
246
+ #
247
+ # If +decimal+ is positive infinity, returns Infinity.
248
+ #
249
+ # If +decimal+ is NaN, returns NaN.
250
+ #
251
+ def self.log(x, prec)
252
+ prec = BigDecimal::Internal.coerce_validate_prec(prec, :log)
253
+ raise Math::DomainError, 'Complex argument for BigMath.log' if Complex === x
254
+
255
+ x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :log)
256
+ return BigDecimal::Internal.nan_computation_result if x.nan?
257
+ raise Math::DomainError, 'Negative argument for log' if x < 0
258
+ return -BigDecimal::Internal.infinity_computation_result if x.zero?
259
+ return BigDecimal::Internal.infinity_computation_result if x.infinite?
260
+ return BigDecimal(0) if x == 1
261
+
262
+ prec2 = prec + BigDecimal.double_fig
263
+ BigDecimal.save_limit do
264
+ BigDecimal.limit(0)
265
+ if x > 10 || x < 0.1
266
+ log10 = log(BigDecimal(10), prec2)
267
+ exponent = x.exponent
268
+ x = x._decimal_shift(-exponent)
269
+ if x < 0.3
270
+ x *= 10
271
+ exponent -= 1
272
+ end
273
+ return (log10 * exponent).add(log(x, prec2), prec)
274
+ end
275
+
276
+ x_minus_one_exponent = (x - 1).exponent
277
+
278
+ # log(x) = log(sqrt(sqrt(sqrt(sqrt(x))))) * 2**sqrt_steps
279
+ sqrt_steps = [Integer.sqrt(prec2) + 3 * x_minus_one_exponent, 0].max
280
+
281
+ lg2 = 0.3010299956639812
282
+ sqrt_prec = prec2 + [-x_minus_one_exponent, 0].max + (sqrt_steps * lg2).ceil
283
+
284
+ sqrt_steps.times do
285
+ x = x.sqrt(sqrt_prec)
286
+ end
287
+
288
+ # Taylor series for log(x) around 1
289
+ # log(x) = -log((1 + X) / (1 - X)) where X = (x - 1) / (x + 1)
290
+ # log(x) = 2 * (X + X**3 / 3 + X**5 / 5 + X**7 / 7 + ...)
291
+ x = (x - 1).div(x + 1, sqrt_prec)
292
+ y = x
293
+ x2 = x.mult(x, prec2)
294
+ 1.step do |i|
295
+ n = prec2 + x.exponent - y.exponent + x2.exponent
296
+ break if n <= 0 || x.zero?
297
+ x = x.mult(x2.round(n - x2.exponent), n)
298
+ y = y.add(x.div(2 * i + 1, n), prec2)
299
+ end
300
+
301
+ y.mult(2 ** (sqrt_steps + 1), prec)
302
+ end
303
+ end
304
+
305
+ # Taylor series for exp(x) around 0
306
+ private_class_method def self._exp_taylor(x, prec) # :nodoc:
307
+ xn = BigDecimal(1)
308
+ y = BigDecimal(1)
309
+ 1.step do |i|
310
+ n = prec + xn.exponent
311
+ break if n <= 0 || xn.zero?
312
+ xn = xn.mult(x, n).div(i, n)
313
+ y = y.add(xn, prec)
314
+ end
315
+ y
316
+ end
317
+
318
+ # call-seq:
319
+ # BigMath.exp(decimal, numeric) -> BigDecimal
320
+ #
321
+ # Computes the value of e (the base of natural logarithms) raised to the
322
+ # power of +decimal+, to the specified number of digits of precision.
323
+ #
324
+ # If +decimal+ is infinity, returns Infinity.
325
+ #
326
+ # If +decimal+ is NaN, returns NaN.
327
+ #
328
+ def self.exp(x, prec)
329
+ prec = BigDecimal::Internal.coerce_validate_prec(prec, :exp)
330
+ x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :exp)
331
+ return BigDecimal::Internal.nan_computation_result if x.nan?
332
+ return x.positive? ? BigDecimal::Internal.infinity_computation_result : BigDecimal(0) if x.infinite?
333
+ return BigDecimal(1) if x.zero?
334
+
335
+ # exp(x * 10**cnt) = exp(x)**(10**cnt)
336
+ cnt = x < -1 || x > 1 ? x.exponent : 0
337
+ prec2 = prec + BigDecimal.double_fig + cnt
338
+ x = x._decimal_shift(-cnt)
339
+
340
+ # Calculation of exp(small_prec) is fast because calculation of x**n is fast
341
+ # Calculation of exp(small_abs) converges fast.
342
+ # exp(x) = exp(small_prec_part + small_abs_part) = exp(small_prec_part) * exp(small_abs_part)
343
+ x_small_prec = x.round(Integer.sqrt(prec2))
344
+ y = _exp_taylor(x_small_prec, prec2).mult(_exp_taylor(x.sub(x_small_prec, prec2), prec2), prec2)
345
+
346
+ # calculate exp(x * 10**cnt) from exp(x)
347
+ # exp(x * 10**k) = exp(x * 10**(k - 1)) ** 10
348
+ cnt.times do
349
+ y2 = y.mult(y, prec2)
350
+ y5 = y2.mult(y2, prec2).mult(y, prec2)
351
+ y = y5.mult(y5, prec2)
352
+ end
353
+
354
+ y.mult(1, prec)
355
+ end
356
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bigdecimal
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.1
4
+ version: 3.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kenta Murata