diff --git a/lib/bigdecimal.rb b/lib/bigdecimal.rb index 370e9c27..6003802b 100644 --- a/lib/bigdecimal.rb +++ b/lib/bigdecimal.rb @@ -76,9 +76,18 @@ def self.newton_loop(prec, initial_precision: BigDecimal.double_fig / 2, safe_ma end end + # Fast and rough conversion to float for mathematical calculations. + # Bigdecimal#to_f is slow when n_significant_digits is large. + # This is because to_f internally converts BigDecimal to String + # to get the exact nearest float representation. + # TODO: Remove this workaround when BigDecimal#to_f is optimized. + def self.fast_to_f(x) # :nodoc: + x.n_significant_digits < 40 ? x.to_f : x.mult(1, 20).to_f + end + # Calculates Math.log(x.to_f) considering large or small exponent def self.float_log(x) # :nodoc: - Math.log(x._decimal_shift(-x.exponent).to_f) + x.exponent * Math.log(10) + Math.log(fast_to_f(x._decimal_shift(-x.exponent))) + x.exponent * Math.log(10) end # Calculating Taylor series sum using binary splitting method @@ -268,7 +277,7 @@ def sqrt(prec) ex = exponent / 2 x = _decimal_shift(-2 * ex) - y = BigDecimal(Math.sqrt(x.to_f), 0) + y = BigDecimal(Math.sqrt(BigDecimal::Internal.fast_to_f(x)), 0) Internal.newton_loop(prec + BigDecimal::Internal::EXTRA_PREC) do |p| y = y.add(x.div(y, p), p).div(2, p) end diff --git a/lib/bigdecimal/math.rb b/lib/bigdecimal/math.rb index 5d4a635b..03e88cea 100644 --- a/lib/bigdecimal/math.rb +++ b/lib/bigdecimal/math.rb @@ -144,7 +144,7 @@ def cbrt(x, prec) x = -x if neg = x < 0 ex = x.exponent / 3 x = x._decimal_shift(-3 * ex) - y = BigDecimal(Math.cbrt(x.to_f), 0) + y = BigDecimal(Math.cbrt(BigDecimal::Internal.fast_to_f(x)), 0) BigDecimal::Internal.newton_loop(prec + BigDecimal::Internal::EXTRA_PREC) do |p| y = (2 * y + x.div(y, p).div(y, p)).div(3, p) end @@ -304,7 +304,7 @@ def atan(x, prec) # Solve tan(y) - x = 0 with Newton's method # Repeat: y -= (tan(y) - x) * cos(y)**2 - y = BigDecimal(Math.atan(x.to_f), 0) + y = BigDecimal(Math.atan(BigDecimal::Internal.fast_to_f(x)), 0) BigDecimal::Internal.newton_loop(n) do |p| s = sin(y, p) c = (1 - s * s).sqrt(p) @@ -605,7 +605,7 @@ def erf(x, prec) return BigDecimal(1) if x > 5000000000 # erf(5000000000) > 1 - 1e-10000000000000000000 if x > 8 - xf = x.to_f + xf = BigDecimal::Internal.fast_to_f(x) log10_erfc = -xf ** 2 / Math.log(10) - Math.log10(xf * Math::PI ** 0.5) erfc_prec = [prec + log10_erfc.ceil, 1].max erfc = _erfc_asymptotic(x, erfc_prec) @@ -647,7 +647,7 @@ def erfc(x, prec) # erfc(x) = 1 - erf(x) < exp(-x**2)/x/sqrt(pi) # Precision of erf(x) needs about log10(exp(-x**2)/x/sqrt(pi)) extra digits log10 = 2.302585092994046 - xf = x.to_f + xf = BigDecimal::Internal.fast_to_f(x) high_prec = prec + BigDecimal::Internal::EXTRA_PREC + ((xf**2 + Math.log(xf) + Math.log(Math::PI)/2) / log10).ceil BigDecimal(1).sub(erf(x, high_prec), prec) end @@ -698,7 +698,7 @@ def erfc(x, prec) # sqrt(2)/2 + k*log(k) - k - 2*k*log(x) < -prec*log(10) # and the left side is minimized when k = x**2. prec += BigDecimal::Internal::EXTRA_PREC - xf = x.to_f + xf = BigDecimal::Internal.fast_to_f(x) kmax = (1..(xf ** 2).floor).bsearch do |k| Math.log(2) / 2 + k * Math.log(k) - k - 2 * k * Math.log(xf) < -prec * Math.log(10) end