@@ -49,6 +49,46 @@ def self.nan_computation_result # :nodoc:
4949 end
5050 BigDecimal ::NAN
5151 end
52+
53+ # Iteration for Newton's method with increasing precision
54+ def self . newton_loop ( prec , initial_precision : 8 , safe_margin : 2 ) # :nodoc:
55+ precs = [ ]
56+ while prec > initial_precision
57+ precs << prec
58+ prec = ( precs . last + 1 ) / 2 + safe_margin
59+ end
60+ precs . reverse_each do |p |
61+ yield p
62+ end
63+ end
64+
65+ # Calculates Math.log(x.to_f) considering large or small exponent
66+ def self . float_log ( x ) # :nodoc:
67+ Math . log ( x . _decimal_shift ( -x . exponent ) . to_f ) + x . exponent * Math . log ( 10 )
68+ end
69+
70+ # Calculating Taylor series sum using binary splitting method
71+ # Calculates f(x) = (x/d0)*(1+(x/d1)*(1+(x/d2)*(1+(x/d3)*(1+...))))
72+ # x.n_significant_digits or ds.size must be small to be performant.
73+ def self . taylor_sum_binary_splitting ( x , ds , prec ) # :nodoc:
74+ fs = ds . map { |d | [ 0 , BigDecimal ( d ) ] }
75+ # fs = [[a0, a1], [b0, b1], [c0, c1], ...]
76+ # f(x) = a0/a1+(x/a1)*(1+b0/b1+(x/b1)*(1+c0/c1+(x/c1)*(1+d0/d1+(x/d1)*(1+...))))
77+ while fs . size > 1
78+ # Merge two adjacent fractions
79+ # from: (1 + a0/a1 + x/a1 * (1 + b0/b1 + x/b1 * rest))
80+ # to: (1 + (a0*b1+x*(b0+b1))/(a1*b1) + (x*x)/(a1*b1) * rest)
81+ xn = xn ? xn . mult ( xn , prec ) : x
82+ fs = fs . each_slice ( 2 ) . map do |( a , b ) |
83+ b ||= [ 0 , BigDecimal ( 1 ) . _decimal_shift ( [ xn . exponent , 0 ] . max + 2 ) ]
84+ [
85+ ( a [ 0 ] * b [ 1 ] ) . add ( xn * ( b [ 0 ] + b [ 1 ] ) , prec ) ,
86+ a [ 1 ] . mult ( b [ 1 ] , prec )
87+ ]
88+ end
89+ end
90+ BigDecimal ( fs [ 0 ] [ 0 ] ) . div ( fs [ 0 ] [ 1 ] , prec )
91+ end
5292 end
5393
5494 # call-seq:
@@ -202,9 +242,7 @@ def sqrt(prec)
202242 ex = exponent / 2
203243 x = _decimal_shift ( -2 * ex )
204244 y = BigDecimal ( Math . sqrt ( x . to_f ) , 0 )
205- precs = [ prec + BigDecimal . double_fig ]
206- precs << 2 + precs . last / 2 while precs . last > BigDecimal . double_fig
207- precs . reverse_each do |p |
245+ Internal . newton_loop ( prec + BigDecimal . double_fig ) do |p |
208246 y = y . add ( x . div ( y , p ) , p ) . div ( 2 , p )
209247 end
210248 y . _decimal_shift ( ex ) . mult ( 1 , prec )
@@ -239,59 +277,32 @@ def self.log(x, prec)
239277 return BigDecimal ( 0 ) if x == 1
240278
241279 prec2 = prec + BigDecimal . double_fig
242- BigDecimal . save_limit do
243- BigDecimal . limit ( 0 )
244- if x > 10 || x < 0.1
245- log10 = log ( BigDecimal ( 10 ) , prec2 )
246- exponent = x . exponent
247- x = x . _decimal_shift ( -exponent )
248- if x < 0.3
249- x *= 10
250- exponent -= 1
251- end
252- return ( log10 * exponent ) . add ( log ( x , prec2 ) , prec )
253- end
254-
255- x_minus_one_exponent = ( x - 1 ) . exponent
256280
257- # log(x) = log(sqrt(sqrt(sqrt(sqrt(x))))) * 2**sqrt_steps
258- sqrt_steps = [ Integer . sqrt ( prec2 ) + 3 * x_minus_one_exponent , 0 ] . max
259-
260- lg2 = 0.3010299956639812
261- sqrt_prec = prec2 + [ -x_minus_one_exponent , 0 ] . max + ( sqrt_steps * lg2 ) . ceil
262-
263- sqrt_steps . times do
264- x = x . sqrt ( sqrt_prec )
265- end
266-
267- # Taylor series for log(x) around 1
268- # log(x) = -log((1 + X) / (1 - X)) where X = (x - 1) / (x + 1)
269- # log(x) = 2 * (X + X**3 / 3 + X**5 / 5 + X**7 / 7 + ...)
270- x = ( x - 1 ) . div ( x + 1 , sqrt_prec )
271- y = x
272- x2 = x . mult ( x , prec2 )
273- 1 . step do |i |
274- n = prec2 + x . exponent - y . exponent + x2 . exponent
275- break if n <= 0 || x . zero?
276- x = x . mult ( x2 . round ( n - x2 . exponent ) , n )
277- y = y . add ( x . div ( 2 * i + 1 , n ) , prec2 )
278- end
281+ if x < 0.1 || x > 10
282+ exponent = ( 3 * x ) . exponent - 1
283+ x = x . _decimal_shift ( -exponent )
284+ return log ( 10 , prec2 ) . mult ( exponent , prec2 ) . add ( log ( x , prec2 ) , prec )
285+ end
279286
280- y . mult ( 2 ** ( sqrt_steps + 1 ) , prec )
287+ # Solve exp(y) - x = 0 with Newton's method
288+ # Repeat: y -= (exp(y) - x) / exp(y)
289+ y = BigDecimal ( BigDecimal ::Internal . float_log ( x ) , 0 )
290+ exp_additional_prec = [ -( x - 1 ) . exponent , 0 ] . max
291+ BigDecimal ::Internal . newton_loop ( prec2 ) do |p |
292+ expy = exp ( y , p + exp_additional_prec )
293+ y = y . sub ( expy . sub ( x , p ) . div ( expy , p ) , p )
281294 end
295+ y . mult ( 1 , prec )
282296 end
283297
284- # Taylor series for exp(x) around 0
285- private_class_method def self . _exp_taylor ( x , prec ) # :nodoc:
286- xn = BigDecimal ( 1 )
287- y = BigDecimal ( 1 )
288- 1 . step do |i |
289- n = prec + xn . exponent
290- break if n <= 0 || xn . zero?
291- xn = xn . mult ( x , n ) . div ( i , n )
292- y = y . add ( xn , prec )
293- end
294- y
298+ private_class_method def self . _exp_binary_splitting ( x , prec ) # :nodoc:
299+ return BigDecimal ( 1 ) if x . zero?
300+ # Find k that satisfies x**k / k! < 10**(-prec)
301+ log10 = Math . log ( 10 )
302+ logx = BigDecimal ::Internal . float_log ( x . abs )
303+ step = ( 1 ..) . bsearch { |k | Math . lgamma ( k + 1 ) [ 0 ] - k * logx > prec * log10 }
304+ # exp(x)-1 = x*(1+x/2*(1+x/3*(1+x/4*(1+x/5*(1+...)))))
305+ 1 + BigDecimal ::Internal . taylor_sum_binary_splitting ( x , [ *1 ..step ] , prec )
295306 end
296307
297308 # call-seq:
@@ -316,11 +327,21 @@ def self.exp(x, prec)
316327 prec2 = prec + BigDecimal . double_fig + cnt
317328 x = x . _decimal_shift ( -cnt )
318329
319- # Calculation of exp(small_prec) is fast because calculation of x**n is fast
320- # Calculation of exp(small_abs) converges fast.
321- # exp(x) = exp(small_prec_part + small_abs_part) = exp(small_prec_part) * exp(small_abs_part)
322- x_small_prec = x . round ( Integer . sqrt ( prec2 ) )
323- y = _exp_taylor ( x_small_prec , prec2 ) . mult ( _exp_taylor ( x . sub ( x_small_prec , prec2 ) , prec2 ) , prec2 )
330+ # Decimal form of bit-burst algorithm
331+ # Calculate exp(x.xxxxxxxxxxxxxxxx) as
332+ # exp(x.xx) * exp(0.00xx) * exp(0.0000xxxx) * exp(0.00000000xxxxxxxx)
333+ x = x . mult ( 1 , prec2 )
334+ n = 2
335+ y = BigDecimal ( 1 )
336+ BigDecimal . save_limit do
337+ BigDecimal . limit ( 0 )
338+ while x != 0 do
339+ partial_x = x . truncate ( n )
340+ x -= partial_x
341+ y = y . mult ( _exp_binary_splitting ( partial_x , prec2 ) , prec2 )
342+ n *= 2
343+ end
344+ end
324345
325346 # calculate exp(x * 10**cnt) from exp(x)
326347 # exp(x * 10**k) = exp(x * 10**(k - 1)) ** 10
0 commit comments