|
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,108 @@ 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 | + # erf(x) = (2 / sqrt(pi)) * exp(-x**2) * sum { 4**k * k! * x**(2k + 1) / (2k + 1)! } |
| 253 | + # Controlling precision of this series is easier than normal erf taylor series. |
| 254 | + prec += BigDecimal.double_fig |
| 255 | + x2 = x.mult(x, prec) |
| 256 | + |
| 257 | + log10 = 2.302585092994046 |
| 258 | + return BigDecimal(x > 0 ? 1 : -1) if x2 > prec * log10 |
| 259 | + |
| 260 | + a = BigDecimal(2).div(sqrt(PI(prec), prec), prec).mult(BigMath.exp(-x2, prec), prec) |
| 261 | + b = x |
| 262 | + d = x |
| 263 | + 1.step do |k| |
| 264 | + d = d.mult(2, prec).div(2 * k + 1, prec).mult(x2, prec) |
| 265 | + b = b.add(d, prec) |
| 266 | + if d.exponent < b.exponent - prec |
| 267 | + break |
| 268 | + end |
| 269 | + end |
| 270 | + v = a.mult(b, prec) |
| 271 | + v > 1 ? BigDecimal(1) : v |
| 272 | + end |
| 273 | + |
| 274 | + # call-seq: |
| 275 | + # erfc(decimal, numeric) -> BigDecimal |
| 276 | + # |
| 277 | + # Computes the complementary error function of +decimal+ to the specified number of digits of |
| 278 | + # precision, +numeric+. |
| 279 | + # |
| 280 | + # If +decimal+ is NaN, returns NaN. |
| 281 | + # |
| 282 | + # BigMath.erfc(BigDecimal('10'), 16).to_s |
| 283 | + # #=> "0.20884875837625447570007862949578e-44" |
| 284 | + # |
| 285 | + def erfc(x, prec) |
| 286 | + raise ArgumentError, "Zero or negative precision for erfc" if prec <= 0 |
| 287 | + return BigDecimal("NaN") if x.nan? |
| 288 | + return BigDecimal(1).sub(erf(x, prec), prec + BigDecimal.double_fig) if x < 0 |
| 289 | + |
| 290 | + if x >= 8 |
| 291 | + # Faster asymptotic expansion can be used if x is large enough |
| 292 | + y = _erfc_asymptotic(x, prec) |
| 293 | + return y if y |
| 294 | + end |
| 295 | + |
| 296 | + prec += BigDecimal.double_fig |
| 297 | + |
| 298 | + # erfc(x) = 1 - erf(x) < exp(-x**2)/x/sqrt(pi) |
| 299 | + # Precision of erf(x) needs about log10(exp(-x**2)) extra digits |
| 300 | + log10 = 2.302585092994046 |
| 301 | + high_prec = prec + (x**2 / log10).ceil |
| 302 | + BigDecimal(1).sub(erf(x, high_prec), prec) |
| 303 | + end |
| 304 | + |
| 305 | + private def _erfc_asymptotic(x, prec) |
| 306 | + # Let f(x) = erfc(x)*sqrt(pi)*exp(x**2)/2 |
| 307 | + # f(x) satisfies the following differential equation: |
| 308 | + # 2*x*f(x) = f'(x) + 1 |
| 309 | + # From the above equation, we can derive the following asymptotic expansion: |
| 310 | + # f(x) = sum { (-1)**k * (2*k)! / 4***k / k! / x**(2*k)) } / x |
| 311 | + |
| 312 | + # This asymptotic expansion does not converge. |
| 313 | + # But if there is a k that satisfies (2*k)! / 4***k / k! / x**(2*k) < 10**(-prec), |
| 314 | + # It is enough to calculate erfc within the given precision. |
| 315 | + # (2*k)! / 4**k / k! can be approximated as sqrt(2) * (k/e)**k by using Stirling's approximation. |
| 316 | + prec += BigDecimal.double_fig |
| 317 | + xf = x.to_f |
| 318 | + log10xf = Math.log10(xf) |
| 319 | + kmax = 1 |
| 320 | + until kmax * Math.log10(kmax / Math::E) + 1 - 2 * kmax * log10xf < -prec |
| 321 | + kmax += 1 |
| 322 | + return if xf * xf < kmax # unable to calculate with the given precision |
| 323 | + end |
| 324 | + |
| 325 | + sum = BigDecimal(1) |
| 326 | + xinv = BigDecimal(1).div(x, prec) |
| 327 | + xinv2 = xinv.mult(xinv, prec) |
| 328 | + d = BigDecimal(1) |
| 329 | + (1..kmax).each do |k| |
| 330 | + d = d.mult(xinv2, prec).mult(1 - 2 * k, prec).div(2, prec) |
| 331 | + sum = sum.add(d, prec) |
| 332 | + end |
| 333 | + expx2 = BigMath.exp(x.mult(x, prec), prec) |
| 334 | + |
| 335 | + # Workaround for https://github.com/ruby/bigdecimal/issues/345 |
| 336 | + expx2 = BigDecimal(expx2) unless expx2.is_a?(BigDecimal) |
| 337 | + |
| 338 | + sum.div(expx2.mult(PI(prec).sqrt(prec), prec), prec).mult(xinv, prec) |
| 339 | + end |
234 | 340 | end
|
0 commit comments