|
8 | 8 | # sin (x, prec) |
9 | 9 | # cos (x, prec) |
10 | 10 | # atan(x, prec) |
| 11 | +# erf (x, prec) |
| 12 | +# erfc(x, prec) |
11 | 13 | # PI (prec) |
12 | 14 | # E (prec) == exp(1.0,prec) |
13 | 15 | # |
@@ -231,4 +233,133 @@ def E(prec) |
231 | 233 | raise ArgumentError, "Zero or negative precision for E" if prec <= 0 |
232 | 234 | BigMath.exp(1, prec) |
233 | 235 | end |
| 236 | + |
| 237 | + # call-seq: |
| 238 | + # erf(decimal, numeric) -> BigDecimal |
| 239 | + # |
| 240 | + # Computes the error function of +decimal+ to the specified number of digits of |
| 241 | + # precision, +numeric+. |
| 242 | + # |
| 243 | + # If +decimal+ is NaN, returns NaN. |
| 244 | + # |
| 245 | + # BigMath.erf(BigDecimal('1'), 16).to_s |
| 246 | + # #=> "0.84270079294971486934122063508259e0" |
| 247 | + # |
| 248 | + def erf(x, prec) |
| 249 | + raise ArgumentError, "Zero or negative precision for erf" if prec <= 0 |
| 250 | + return BigDecimal("NaN") if x.nan? |
| 251 | + return BigDecimal(0) if x == 0 |
| 252 | + return -erf(-x, prec) if x < 0 |
| 253 | + |
| 254 | + prec += BigDecimal.double_fig |
| 255 | + |
| 256 | + if x > 8 && (erfc1 = _erfc_asymptotic(x.abs, 1)) |
| 257 | + erfc2 = _erfc_asymptotic(x.abs, [prec + erfc1.exponent, 1].max) |
| 258 | + return BigDecimal(1).sub(erfc2, prec) if erfc2 |
| 259 | + end |
| 260 | + |
| 261 | + base = BigDecimal::BASE ** 2 |
| 262 | + x_smallprec = (x * base).fix / base |
| 263 | + # Taylor series of x with small precision is fast |
| 264 | + erf1 = _erf_taylor(x_smallprec, BigDecimal(0), BigDecimal(0), prec) |
| 265 | + # Taylor series converges quickly for small x |
| 266 | + v = _erf_taylor(x - x_smallprec, x_smallprec, erf1, prec) |
| 267 | + [BigDecimal(1), v].min |
| 268 | + end |
| 269 | + |
| 270 | + # call-seq: |
| 271 | + # erfc(decimal, numeric) -> BigDecimal |
| 272 | + # |
| 273 | + # Computes the complementary error function of +decimal+ to the specified number of digits of |
| 274 | + # precision, +numeric+. |
| 275 | + # |
| 276 | + # If +decimal+ is NaN, returns NaN. |
| 277 | + # |
| 278 | + # BigMath.erfc(BigDecimal('10'), 16).to_s |
| 279 | + # #=> "0.20884875837625447570007862949578e-44" |
| 280 | + # |
| 281 | + def erfc(x, prec) |
| 282 | + raise ArgumentError, "Zero or negative precision for erfc" if prec <= 0 |
| 283 | + return BigDecimal("NaN") if x.nan? |
| 284 | + return BigDecimal(1).sub(erf(x, prec), prec + BigDecimal.double_fig) if x < 0 |
| 285 | + |
| 286 | + if x >= 8 |
| 287 | + y = _erfc_asymptotic(x, prec) |
| 288 | + return y if y |
| 289 | + end |
| 290 | + |
| 291 | + prec += BigDecimal.double_fig |
| 292 | + |
| 293 | + # erfc(x) = 1 - erf(x) < exp(-x**2)/x/sqrt(pi) |
| 294 | + # Precision of erf(x) needs about log10(exp(-x**2)) extra digits |
| 295 | + log10 = 2.302585092994046 |
| 296 | + high_prec = prec + (x**2 / log10).ceil |
| 297 | + BigDecimal(1).sub(erf(x, high_prec), prec) |
| 298 | + end |
| 299 | + |
| 300 | + |
| 301 | + private def _erf_taylor(x, a, erf_a, prec) |
| 302 | + # Let f(x) = erf(x+a)*exp((x+a)**2)*sqrt(pi)/2 |
| 303 | + # = c0 + c1*x + c2*x**2 + c3*x**3 + c4*x**4 + ... |
| 304 | + # f'(x) = 1+2*(x+a)*f(x) |
| 305 | + # f'(x) = c1 + 2*c2*x + 3*c3*x**2 + 4*c4*x**3 + 5*c5*x**4 + ... |
| 306 | + # = 1+2*(x+a)*(c0 + c1*x + c2*x**2 + c3*x**3 + c4*x**4 + ...) |
| 307 | + # therefore, |
| 308 | + # c0 = f(0) |
| 309 | + # c1 = 2 * a * c0 + 1 |
| 310 | + # c2 = (2 * c0 + 2 * a * c1) / 2 |
| 311 | + # c3 = (2 * c1 + 2 * a * c2) / 3 |
| 312 | + # c4 = (2 * c2 + 2 * a * c3) / 4 |
| 313 | + |
| 314 | + return erf_a if x.zero? |
| 315 | + |
| 316 | + scale = BigDecimal(2).div(sqrt(PI(prec), prec), prec) |
| 317 | + c_prev = erf_a.div(scale.mult(BigMath.exp(-a*a, prec), prec), prec) |
| 318 | + c_next = (2 * a * c_prev).add(1, prec).mult(x, prec) |
| 319 | + v = c_prev.add(c_next, prec) |
| 320 | + |
| 321 | + 2.step do |k| |
| 322 | + c = (c_prev.mult(x, prec) + a * c_next).mult(2, prec).mult(x, prec).div(k, prec) |
| 323 | + v = v.add(c, prec) |
| 324 | + c_prev, c_next = c_next, c |
| 325 | + break if [c_prev, c_next].all? { |c| c.zero? || (c.exponent < v.exponent - prec) } |
| 326 | + end |
| 327 | + v = v.mult(scale.mult(BigMath.exp(-(x + a).mult(x + a, prec), prec), prec), prec) |
| 328 | + v > 1 ? BigDecimal(1) : v |
| 329 | + end |
| 330 | + |
| 331 | + private def _erfc_asymptotic(x, prec) |
| 332 | + # Let f(x) = erfc(x)*sqrt(pi)*exp(x**2)/2 |
| 333 | + # f(x) satisfies the following differential equation: |
| 334 | + # 2*x*f(x) = f'(x) + 1 |
| 335 | + # From the above equation, we can derive the following asymptotic expansion: |
| 336 | + # f(x) = sum { (-1)**k * (2*k)! / 4***k / k! / x**(2*k)) } / x |
| 337 | + |
| 338 | + # This asymptotic expansion does not converge. |
| 339 | + # But if there is a k that satisfies (2*k)! / 4***k / k! / x**(2*k) < 10**(-prec), |
| 340 | + # It is enough to calculate erfc within the given precision. |
| 341 | + # (2*k)! / 4**k / k! can be approximated as sqrt(2) * (k/e)**k by using Stirling's approximation. |
| 342 | + prec += BigDecimal.double_fig |
| 343 | + xf = x.to_f |
| 344 | + log10xf = Math.log10(xf) |
| 345 | + kmax = 1 |
| 346 | + until kmax * Math.log10(kmax / Math::E) + 1 - 2 * kmax * log10xf < -prec |
| 347 | + kmax += 1 |
| 348 | + return if xf * xf < kmax # Unable to calculate with the given precision |
| 349 | + end |
| 350 | + |
| 351 | + sum = BigDecimal(1) |
| 352 | + x2 = x.mult(x, prec) |
| 353 | + d = BigDecimal(1) |
| 354 | + (1..kmax).each do |k| |
| 355 | + d = d.div(x2, prec).mult(1 - 2 * k, prec).div(2, prec) |
| 356 | + sum = sum.add(d, prec) |
| 357 | + end |
| 358 | + expx2 = BigMath.exp(x.mult(x, prec), prec) |
| 359 | + |
| 360 | + # Workaround for https://github.com/ruby/bigdecimal/issues/345 |
| 361 | + expx2 = BigDecimal(expx2) unless expx2.is_a?(BigDecimal) |
| 362 | + |
| 363 | + sum.div(expx2.mult(PI(prec).sqrt(prec), prec), prec).div(x, prec) |
| 364 | + end |
234 | 365 | end |
0 commit comments