module QUADPREC_ERRORFUNC use QUADRUPLE_PRECISION implicit none private public :: Q_ERF contains subroutine Q_ERF(X, ERF, ERFC) ! Calculate the error function & its complement in quadruple precision. ! 0 <= x <= 1 uses series 7.1.5 from Abramowitz & Stegun ! 1 < x < 4 uses Chebyshev series ! 4 <= x uses a continued fraction ! ! If x < 0 then erfc(x) = 1 + erf(|x|) and erf(x) = - erf(|x|) ! ! WARNING: x must NOT occupy the same locations as any of ! the other arguments if x < 0. ! ! Programmer: Alan Miller Alan.Miller @ vic.cmis.csiro.au ! www.ozemail.com.au/~milleraj FAX: (+61) 3-9545-8080 ! Latest revision - (Fortran 77 version) 11 May 1988 ! Latest revision - 4 February 1998 type (QUAD), intent(in) :: X type (QUAD), intent(out), optional :: ERF, ERFC ! Chebyshev coefficients type (QUAD), dimension(40), parameter :: COEFF = & (/ QUAD(0.4888515056984716e+00_DP,-0.1253349490474187e-15_DP), QUAD(-0.1358986289159460e+00_DP, 0.1828677302762691e-16_DP), & QUAD(0.3563511414853497e-01_DP,-0.1026978994630672e-17_DP), QUAD(-0.8884098618222492e-02_DP, 0.1869288268188562e-17_DP), & QUAD(0.2118423997553177e-02_DP, 0.1709987556862464e-18_DP), QUAD(-0.4853987640800370e-03_DP,-0.6960364726525852e-19_DP), & QUAD(0.1072737545377177e-03_DP, 0.1534550435363124e-19_DP), QUAD(-0.2293663448190487e-04_DP, 0.6928883131080608e-20_DP), & QUAD(0.4756874691575556e-05_DP,-0.1627977086918653e-20_DP), QUAD(-0.9589944832662770e-06_DP,-0.2249363452839194e-22_DP), & QUAD(0.1882900688295897e-06_DP,-0.1968876883111176e-22_DP), QUAD(-0.3606320662143092e-07_DP, 0.7483138202418590e-23_DP), & QUAD(0.6747602051937456e-08_DP,-0.1005646109123554e-23_DP), QUAD(-0.1234907042333986e-08_DP,-0.8553919151789978e-25_DP), & QUAD(0.2213150119337267e-09_DP,-0.8378636504284192e-27_DP), QUAD(-0.3887955444797815e-10_DP, 0.4599438293044550e-27_DP), & QUAD(0.6701388253274512e-11_DP,-0.4533239266031100e-27_DP), QUAD(-0.1134237426103359e-11_DP,-0.2592968654684961e-27_DP), & QUAD(0.1886555920176928e-12_DP,-0.9535048043067878e-29_DP), QUAD(-0.3085780489354876e-13_DP, 0.7801557018723816e-29_DP), & QUAD(0.4966710292058390e-14_DP,-0.1626370800837245e-29_DP), QUAD(-0.7871154824211008e-15_DP, 0.8389350837750800e-31_DP), & QUAD(0.1228887884514987e-15_DP,-0.5265492467954702e-31_DP), QUAD(-0.1891087633190591e-16_DP,-0.2388153131040173e-32_DP), & QUAD(0.2869742042877464e-17_DP, 0.7703719777548944e-34_DP), QUAD(-0.4296338289538412e-18_DP, 0.9629649721936182e-35_DP), & QUAD(0.6348319360536490e-19_DP,-0.7222237291452136e-35_DP), QUAD(-0.9261746469308854e-20_DP,-0.1805559322863034e-35_DP), & QUAD(0.1334626827242897e-20_DP, 0.2633107345841924e-36_DP), QUAD(-0.1900244302571750e-21_DP, 0.3761581922631320e-37_DP), & QUAD(0.2674133030089899e-22_DP,-0.4701977403289150e-38_DP), QUAD(-0.3720610299437181e-23_DP,-0.2938735877055719e-39_DP), & QUAD(0.5119522849758822e-24_DP,-0.1578156674465586e-56_DP), QUAD(-0.6968654298053806e-25_DP,-0.2295887403949780e-41_DP), & QUAD(0.9386117844916958e-26_DP,-0.2008901478456058e-41_DP), QUAD(-0.1251273661158375e-26_DP, 0.5022253696140144e-42_DP), & QUAD(0.1651286941713772e-27_DP,-0.3587324068671532e-43_DP), QUAD(-0.2157623168556909e-28_DP,-0.5605193857299268e-45_DP), & QUAD(0.2771990968956549e-29_DP,-0.3503246160812043e-45_DP), QUAD(-0.3468792422835851e-30_DP, 0.7006492321624086e-46_DP) /) ! Local variables type (QUAD) :: D, DD, TERM, ARG, Y2, SV, XX, Y, TEMP, ERFF, ERFCC real (kind=DP) :: A integer :: M, J logical :: SMALL type (QUAD), parameter :: QONE = QUAD(1.0_DP, 0.0_DP), & QZERO = QUAD(0.0_DP, 0.0_DP) if (X%HI < 0.0_DP) then ARG = -X else ARG = X end if if (ARG%HI <= 1.0_DP) then !---------------------------------------------------------------------- ! ! 2x x^2 x^4 x^6 ! erf(x) = --------.(1 - --- + ---- - ---- + .. ) ! sqrt(pi) 1.3 2!.5 3!.7 ! ! SMALL = .false. TERM = TWO_ON_RTPI * ARG ERFF = TERM XX = ARG * ARG M = 1 do if (SMALL) then TERM%HI = - TERM%HI * XX%HI / M TEMP%HI = TERM%HI / (2*M + 1) TEMP%LO = 0.0_DP else TERM = - TERM * XX select case (M) case (1) TERM = TERM case (2) TERM = scale(TERM, -1) case (4) TERM = scale(TERM, -2) case (8) TERM = scale(TERM, -3) case default TERM = TERM / real(M, kind=DP) end select TEMP = TERM / real(2*M+1, kind=DP) end if ERFF = ERFF + TEMP M = M + 1 if (.not. SMALL .and. abs(TEMP%HI) < ERFF%HI * 1.0e-16_DP) then SMALL = .true. end if if (abs(TEMP%HI) < ERFF%HI * 1.0e-30_DP) then exit end if end do ERFCC = QONE - ERFF !---------------------------------------------------------------------- ! ! Use Chebyshev series for 1 < x < 4. ! Adapted from the routine CHEBEV from 'Numerical Recipes' by Press, ! Flannery, Teukolsky & Vetterling, Cambridge Uni Press, 1986. ! else if (ARG%HI > 1.0_DP .and. ARG%HI < 4.0_DP) then D = QZERO DD = QZERO Y = (ARG - 2.5_DP) / 1.5_DP Y2 = scale(Y, 1) do J = 40, 2, -1 SV = D D = Y2 * D - DD + COEFF(J) DD = SV end do TERM = Y * D - DD + scale(COEFF(1), -1) ! Finally multiply by exp(-x^2) ERFCC = TERM * exp(-X*X) ERFF = QONE - ERFCC !---------------------------------------------------------------------- ! ! Use a continued fraction for x >= 4.0 else A = 0.5_DP * int(230.0_DP/ARG%HI) + 2.0_DP ERFCC = QZERO do ERFCC = ERFCC + ARG ERFCC = QUAD(A, 0.0_DP) / ERFCC A = A - 0.5_DP if (A < 0.1_DP) then exit end if end do ERFCC = ERFCC + ARG ERFCC = QONE / ERFCC ! Multiply result by exp(-x**2) / sqrt(pi) ERFCC = ERFCC * exp(-X*X) / SQRTPI ERFF = QONE - ERFCC end if !---------------------------------------------------------------------- ! Replace erf & erfc if argument was negative. if (X%HI < 0.0_DP) then ERFCC = QONE + ERFF ERFF = -ERFF end if if (present(ERF)) then ERF = ERFF end if if (present(ERFC)) then ERFC = ERFCC end if return end subroutine Q_ERF end module QUADPREC_ERRORFUNC program TEST_Q_ERF ! Test quadruple-precision erf function by approximating its first derivative ! using divided differences. ! f'(x) = [f(x-2h) - 8.f(x-h) + 8.f(x+h) - f(x-2h)] / (12.h) approx. use QUADRUPLE_PRECISION use QUADPREC_ERRORFUNC implicit none type (QUAD) :: X, H, ARG, ERF_2, ERF_1, ERF1, ERF2, ERFC, AV_ERFC, & D_EST, DERIV, ERROR type (QUAD), parameter :: QONE = QUAD(1.0_DP, 0.0_DP) do write(unit=*, fmt="(a)", advance="NO") " Enter x: " read(unit=*, fmt=*) X%HI X%LO = 0.0_DP H = scale(QONE, -24) if (abs(X%HI) < 0.477_DP) then ! Use erf if erf(x) > 0.5 ARG = X - 2.0_DP * H call Q_ERF(ARG, ERF_2, AV_ERFC) ARG = X - H call Q_ERF(ARG, ERF_1, ERFC) AV_ERFC = AV_ERFC + ERFC ARG = X + H call Q_ERF(ARG, ERF1, ERFC) AV_ERFC = AV_ERFC + ERFC ARG = X + 2.0_DP * H call Q_ERF(ARG, ERF2, ERFC) AV_ERFC = (AV_ERFC + ERFC) / 4.0_DP D_EST = (ERF_2 - ERF2 + scale( ERF1 - ERF_1, 3)) / (12.0_DP * H) else ! Else use erfc(x) ARG = X - 2.0_DP * H call Q_ERF(ARG, ERFC=AV_ERFC) ERF_2 = AV_ERFC ARG = X - H call Q_ERF(ARG, ERFC=ERF_1) AV_ERFC = AV_ERFC + ERF_1 ARG = X + H call Q_ERF(ARG, ERFC=ERF1) AV_ERFC = AV_ERFC + ERF1 ARG = X + 2.0_DP * H call Q_ERF(ARG, ERFC=ERF2) AV_ERFC = (AV_ERFC + ERF2) / 4.0_DP D_EST = (ERF2 - ERF_2 + scale( ERF_1 - ERF1, 3)) / (12.0_DP * H) end if DERIV = TWO_ON_RTPI * exp(-X*X) ERROR = D_EST - DERIV write(unit=*, fmt="(a)") " Estimate Derivative Error erfc" write(unit=*, fmt="(2f20.16, g12.4, f20.16)") & D_EST%HI, DERIV%HI, ERROR%HI, AV_ERFC%HI end do stop end program TEST_Q_ERF