@@ -60,6 +60,46 @@ def self.nan_computation_result # :nodoc:
6060 end
6161 BigDecimal ::NAN
6262 end
63+
64+ # Iteration for Newton's method with increasing precision
65+ def self . newton_loop ( prec , initial_precision : BigDecimal . double_fig / 2 , safe_margin : 2 ) # :nodoc:
66+ precs = [ ]
67+ while prec > initial_precision
68+ precs << prec
69+ prec = ( precs . last + 1 ) / 2 + safe_margin
70+ end
71+ precs . reverse_each do |p |
72+ yield p
73+ end
74+ end
75+
76+ # Calculates Math.log(x.to_f) considering large or small exponent
77+ def self . float_log ( x ) # :nodoc:
78+ Math . log ( x . _decimal_shift ( -x . exponent ) . to_f ) + x . exponent * Math . log ( 10 )
79+ end
80+
81+ # Calculating Taylor series sum using binary splitting method
82+ # Calculates f(x) = (x/d0)*(1+(x/d1)*(1+(x/d2)*(1+(x/d3)*(1+...))))
83+ # x.n_significant_digits or ds.size must be small to be performant.
84+ def self . taylor_sum_binary_splitting ( x , ds , prec ) # :nodoc:
85+ fs = ds . map { |d | [ 0 , BigDecimal ( d ) ] }
86+ # fs = [[a0, a1], [b0, b1], [c0, c1], ...]
87+ # f(x) = a0/a1+(x/a1)*(1+b0/b1+(x/b1)*(1+c0/c1+(x/c1)*(1+d0/d1+(x/d1)*(1+...))))
88+ while fs . size > 1
89+ # Merge two adjacent fractions
90+ # from: (1 + a0/a1 + x/a1 * (1 + b0/b1 + x/b1 * rest))
91+ # to: (1 + (a0*b1+x*(b0+b1))/(a1*b1) + (x*x)/(a1*b1) * rest)
92+ xn = xn ? xn . mult ( xn , prec ) : x
93+ fs = fs . each_slice ( 2 ) . map do |( a , b ) |
94+ b ||= [ 0 , BigDecimal ( 1 ) . _decimal_shift ( [ xn . exponent , 0 ] . max + 2 ) ]
95+ [
96+ ( a [ 0 ] * b [ 1 ] ) . add ( xn * ( b [ 0 ] + b [ 1 ] ) , prec ) ,
97+ a [ 1 ] . mult ( b [ 1 ] , prec )
98+ ]
99+ end
100+ end
101+ BigDecimal ( fs [ 0 ] [ 0 ] ) . div ( fs [ 0 ] [ 1 ] , prec )
102+ end
63103 end
64104
65105 # call-seq:
@@ -226,9 +266,7 @@ def sqrt(prec)
226266 ex = exponent / 2
227267 x = _decimal_shift ( -2 * ex )
228268 y = BigDecimal ( Math . sqrt ( x . to_f ) , 0 )
229- precs = [ prec + BigDecimal . double_fig ]
230- precs << 2 + precs . last / 2 while precs . last > BigDecimal . double_fig
231- precs . reverse_each do |p |
269+ Internal . newton_loop ( prec + BigDecimal . double_fig ) do |p |
232270 y = y . add ( x . div ( y , p ) , p ) . div ( 2 , p )
233271 end
234272 y . _decimal_shift ( ex ) . mult ( 1 , prec )
@@ -264,59 +302,32 @@ def log(x, prec)
264302 return BigDecimal ( 0 ) if x == 1
265303
266304 prec2 = prec + BigDecimal . double_fig
267- BigDecimal . save_limit do
268- BigDecimal . limit ( 0 )
269- if x > 10 || x < 0.1
270- log10 = log ( BigDecimal ( 10 ) , prec2 )
271- exponent = x . exponent
272- x = x . _decimal_shift ( -exponent )
273- if x < 0.3
274- x *= 10
275- exponent -= 1
276- end
277- return ( log10 * exponent ) . add ( log ( x , prec2 ) , prec )
278- end
279-
280- x_minus_one_exponent = ( x - 1 ) . exponent
281305
282- # log(x) = log(sqrt(sqrt(sqrt(sqrt(x))))) * 2**sqrt_steps
283- sqrt_steps = [ Integer . sqrt ( prec2 ) + 3 * x_minus_one_exponent , 0 ] . max
284-
285- lg2 = 0.3010299956639812
286- sqrt_prec = prec2 + [ -x_minus_one_exponent , 0 ] . max + ( sqrt_steps * lg2 ) . ceil
287-
288- sqrt_steps . times do
289- x = x . sqrt ( sqrt_prec )
290- end
291-
292- # Taylor series for log(x) around 1
293- # log(x) = -log((1 + X) / (1 - X)) where X = (x - 1) / (x + 1)
294- # log(x) = 2 * (X + X**3 / 3 + X**5 / 5 + X**7 / 7 + ...)
295- x = ( x - 1 ) . div ( x + 1 , sqrt_prec )
296- y = x
297- x2 = x . mult ( x , prec2 )
298- 1 . step do |i |
299- n = prec2 + x . exponent - y . exponent + x2 . exponent
300- break if n <= 0 || x . zero?
301- x = x . mult ( x2 . round ( n - x2 . exponent ) , n )
302- y = y . add ( x . div ( 2 * i + 1 , n ) , prec2 )
303- end
306+ if x < 0.1 || x > 10
307+ exponent = ( 3 * x ) . exponent - 1
308+ x = x . _decimal_shift ( -exponent )
309+ return log ( 10 , prec2 ) . mult ( exponent , prec2 ) . add ( log ( x , prec2 ) , prec )
310+ end
304311
305- y . mult ( 2 ** ( sqrt_steps + 1 ) , prec )
312+ # Solve exp(y) - x = 0 with Newton's method
313+ # Repeat: y -= (exp(y) - x) / exp(y)
314+ y = BigDecimal ( BigDecimal ::Internal . float_log ( x ) , 0 )
315+ exp_additional_prec = [ -( x - 1 ) . exponent , 0 ] . max
316+ BigDecimal ::Internal . newton_loop ( prec2 ) do |p |
317+ expy = exp ( y , p + exp_additional_prec )
318+ y = y . sub ( expy . sub ( x , p ) . div ( expy , p ) , p )
306319 end
320+ y . mult ( 1 , prec )
307321 end
308322
309- # Taylor series for exp(x) around 0
310- private_class_method def _exp_taylor ( x , prec ) # :nodoc:
311- xn = BigDecimal ( 1 )
312- y = BigDecimal ( 1 )
313- 1 . step do |i |
314- n = prec + xn . exponent
315- break if n <= 0 || xn . zero?
316- xn = xn . mult ( x , n ) . div ( i , n )
317- y = y . add ( xn , prec )
318- end
319- y
323+ private_class_method def _exp_binary_splitting ( x , prec ) # :nodoc:
324+ return BigDecimal ( 1 ) if x . zero?
325+ # Find k that satisfies x**k / k! < 10**(-prec)
326+ log10 = Math . log ( 10 )
327+ logx = BigDecimal ::Internal . float_log ( x . abs )
328+ step = ( 1 ..) . bsearch { |k | Math . lgamma ( k + 1 ) [ 0 ] - k * logx > prec * log10 }
329+ # exp(x)-1 = x*(1+x/2*(1+x/3*(1+x/4*(1+x/5*(1+...)))))
330+ 1 + BigDecimal ::Internal . taylor_sum_binary_splitting ( x , [ *1 ..step ] , prec )
320331 end
321332
322333 # call-seq:
@@ -341,11 +352,21 @@ def exp(x, prec)
341352 prec2 = prec + BigDecimal . double_fig + cnt
342353 x = x . _decimal_shift ( -cnt )
343354
344- # Calculation of exp(small_prec) is fast because calculation of x**n is fast
345- # Calculation of exp(small_abs) converges fast.
346- # exp(x) = exp(small_prec_part + small_abs_part) = exp(small_prec_part) * exp(small_abs_part)
347- x_small_prec = x . round ( Integer . sqrt ( prec2 ) )
348- y = _exp_taylor ( x_small_prec , prec2 ) . mult ( _exp_taylor ( x . sub ( x_small_prec , prec2 ) , prec2 ) , prec2 )
355+ # Decimal form of bit-burst algorithm
356+ # Calculate exp(x.xxxxxxxxxxxxxxxx) as
357+ # exp(x.xx) * exp(0.00xx) * exp(0.0000xxxx) * exp(0.00000000xxxxxxxx)
358+ x = x . mult ( 1 , prec2 )
359+ n = 2
360+ y = BigDecimal ( 1 )
361+ BigDecimal . save_limit do
362+ BigDecimal . limit ( 0 )
363+ while x != 0 do
364+ partial_x = x . truncate ( n )
365+ x -= partial_x
366+ y = y . mult ( _exp_binary_splitting ( partial_x , prec2 ) , prec2 )
367+ n *= 2
368+ end
369+ end
349370
350371 # calculate exp(x * 10**cnt) from exp(x)
351372 # exp(x * 10**k) = exp(x * 10**(k - 1)) ** 10
0 commit comments