Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions lib/bigdecimal/math.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
# cos (x, prec)
# tan (x, prec)
# atan(x, prec)
# erf (x, prec)
# erfc(x, prec)
# PI (prec)
# E (prec) == exp(1.0,prec)
#
Expand Down Expand Up @@ -246,4 +248,130 @@ def E(prec)
BigDecimal::Internal.validate_prec(prec, :E)
BigMath.exp(1, prec)
end

# call-seq:
# erf(decimal, numeric) -> BigDecimal
#
# Computes the error function of +decimal+ to the specified number of digits of
# precision, +numeric+.
#
# If +decimal+ is NaN, returns NaN.
#
# BigMath.erf(BigDecimal('1'), 32).to_s
# #=> "0.84270079294971486934122063508261e0"
#
def erf(x, prec)
BigDecimal::Internal.validate_prec(prec, :erf)
x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :erf)
return BigDecimal::Internal.nan_computation_result if x.nan?
return BigDecimal(x.infinite?) if x.infinite?
return BigDecimal(0) if x == 0
return -erf(-x, prec) if x < 0

if x > 8 && (erfc1 = _erfc_asymptotic(x.abs, 1))
erfc2 = _erfc_asymptotic(x.abs, [prec + erfc1.exponent, 1].max)
return BigDecimal(1).sub(erfc2, prec) if erfc2
end

prec2 = prec + BigDecimal.double_fig
base = BigDecimal::BASE ** 2
x_smallprec = (x * base).fix / base
# Taylor series of x with small precision is fast
erf1 = _erf_taylor(x_smallprec, BigDecimal(0), BigDecimal(0), prec2)
# Taylor series converges quickly for small x
v = _erf_taylor(x - x_smallprec, x_smallprec, erf1, prec2).mult(1, prec)
[BigDecimal(1), v].min
end

# call-seq:
# erfc(decimal, numeric) -> BigDecimal
#
# Computes the complementary error function of +decimal+ to the specified number of digits of
# precision, +numeric+.
#
# If +decimal+ is NaN, returns NaN.
#
# BigMath.erfc(BigDecimal('10'), 32).to_s
# #=> "0.20884875837625447570007862949578e-44"
#
def erfc(x, prec)
BigDecimal::Internal.validate_prec(prec, :erfc)
x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :erfc)
return BigDecimal::Internal.nan_computation_result if x.nan?
return BigDecimal(1 - x.infinite?) if x.infinite?
return BigDecimal(1).sub(erf(x, prec), prec) if x < 0

if x >= 8
y = _erfc_asymptotic(x, prec)
return y.mult(1, prec) if y
end

# erfc(x) = 1 - erf(x) < exp(-x**2)/x/sqrt(pi)
# Precision of erf(x) needs about log10(exp(-x**2)) extra digits
log10 = 2.302585092994046
high_prec = prec + BigDecimal.double_fig + (x**2 / log10).ceil
BigDecimal(1).sub(erf(x, high_prec), prec)
end


private def _erf_taylor(x, a, erf_a, prec)
# Let f(x) = erf(x+a)*exp((x+a)**2)*sqrt(pi)/2
# = c0 + c1*x + c2*x**2 + c3*x**3 + c4*x**4 + ...
# f'(x) = 1+2*(x+a)*f(x)
# f'(x) = c1 + 2*c2*x + 3*c3*x**2 + 4*c4*x**3 + 5*c5*x**4 + ...
# = 1+2*(x+a)*(c0 + c1*x + c2*x**2 + c3*x**3 + c4*x**4 + ...)
# therefore,
# c0 = f(0)
# c1 = 2 * a * c0 + 1
# c2 = (2 * c0 + 2 * a * c1) / 2
# c3 = (2 * c1 + 2 * a * c2) / 3
# c4 = (2 * c2 + 2 * a * c3) / 4

return erf_a if x.zero?

scale = BigDecimal(2).div(sqrt(PI(prec), prec), prec)
c_prev = erf_a.div(scale.mult(BigMath.exp(-a*a, prec), prec), prec)
c_next = (2 * a * c_prev).add(1, prec).mult(x, prec)
v = c_prev.add(c_next, prec)

2.step do |k|
c = (c_prev.mult(x, prec) + a * c_next).mult(2, prec).mult(x, prec).div(k, prec)
v = v.add(c, prec)
c_prev, c_next = c_next, c
break if [c_prev, c_next].all? { |c| c.zero? || (c.exponent < v.exponent - prec) }
end
v = v.mult(scale.mult(BigMath.exp(-(x + a).mult(x + a, prec), prec), prec), prec)
v > 1 ? BigDecimal(1) : v
end

private def _erfc_asymptotic(x, prec)
# Let f(x) = erfc(x)*sqrt(pi)*exp(x**2)/2
# f(x) satisfies the following differential equation:
# 2*x*f(x) = f'(x) + 1
# From the above equation, we can derive the following asymptotic expansion:
# f(x) = sum { (-1)**k * (2*k)! / 4***k / k! / x**(2*k)) } / x

# This asymptotic expansion does not converge.
# But if there is a k that satisfies (2*k)! / 4***k / k! / x**(2*k) < 10**(-prec),
# It is enough to calculate erfc within the given precision.
# (2*k)! / 4**k / k! can be approximated as sqrt(2) * (k/e)**k by using Stirling's approximation.
prec += BigDecimal.double_fig
xf = x.to_f
log10xf = Math.log10(xf)
kmax = 1
until kmax * Math.log10(kmax / Math::E) + 1 - 2 * kmax * log10xf < -prec
kmax += 1
return if xf * xf < kmax # Unable to calculate with the given precision
end

sum = BigDecimal(1)
x2 = x.mult(x, prec)
d = BigDecimal(1)
(1..kmax).each do |k|
d = d.div(x2, prec).mult(1 - 2 * k, prec).div(2, prec)
sum = sum.add(d, prec)
end
expx2 = BigMath.exp(x.mult(x, prec), prec)
sum.div(expx2.mult(PI(prec).sqrt(prec), prec), prec).div(x, prec)
end
end
44 changes: 44 additions & 0 deletions test/bigdecimal/test_bigmath.rb
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,48 @@ def test_log
end
SRC
end

def test_erf
[-0.5, 0.1, 0.3, 2.1, 3.3].each do |x|
assert_in_epsilon(Math.erf(x), BigMath.erf(BigDecimal(x.to_s), N))
end
assert_equal(1, BigMath.erf(PINF, N))
assert_equal(-1, BigMath.erf(MINF, N))
assert_equal(1, BigMath.erf(BigDecimal(1000), 100))
assert_equal(-1, BigMath.erf(BigDecimal(-1000), 100))
assert_not_equal(1, BigMath.erf(BigDecimal(10), 45))
assert_not_equal(1, BigMath.erf(BigDecimal(15), 100))
assert_equal(
BigDecimal("0.9953222650189527341620692563672529286108917970400600767383523262004372807199951773676290080196806805"),
BigMath.erf(BigDecimal("2"), 100)
)
assert_converge_in_precision {|n| BigMath.erf(BigDecimal("1e-30"), n) }
assert_converge_in_precision {|n| BigMath.erf(BigDecimal("0.3"), n) }
assert_converge_in_precision {|n| BigMath.erf(SQRT2, n) }
end

def test_erfc
[-0.5, 0.1, 0.3, 2.1, 3.3].each do |x|
assert_in_epsilon(Math.erfc(x), BigMath.erfc(BigDecimal(x.to_s), N))
end
assert_equal(0, BigMath.erfc(PINF, N))
assert_equal(2, BigMath.erfc(MINF, N))

# erfc with taylor series
assert_equal(
BigDecimal("2.088487583762544757000786294957788611560818119321163727012213713938174695833440290610766384285723554e-45"),
BigMath.erfc(BigDecimal("10"), 100)
)
assert_converge_in_precision {|n| BigMath.erfc(BigDecimal("0.3"), n) }
assert_converge_in_precision {|n| BigMath.erfc(SQRT2, n) }
assert_converge_in_precision {|n| BigMath.erfc(BigDecimal("8"), n) }
# erfc with asymptotic expansion
assert_equal(
BigDecimal("1.896961059966276509268278259713415434936907563929186183462834752900411805205111886605256690776760041e-697"),
BigMath.erfc(BigDecimal("40"), 100)
)
assert_converge_in_precision {|n| BigMath.erfc(BigDecimal("30"), n) }
assert_converge_in_precision {|n| BigMath.erfc(30 * SQRT2, n) }
assert_converge_in_precision {|n| BigMath.erfc(BigDecimal("50"), n) }
end
end
Loading