/*  $Id: boost_erf.c 498248 2016-04-14 15:42:26Z boratyng $
* ===========================================================================
*
* This code is ported from the open source C++ library Boost, for which
* the following notice applies:
*
* (C) Copyright John Maddock 2006.
* Use, modification and distribution are subject to the Boost Software
* License, Version 1.0. (See below or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*
* Boost Software License - Version 1.0 - August 17th, 2003
*
* Permission is hereby granted, free of charge, to any person or organization
* obtaining a copy of the software and accompanying documentation covered by
* this license (the "Software") to use, reproduce, display, distribute,
* execute, and transmit the Software, and to prepare derivative works of the
* Software, and to permit third-parties to whom the Software is furnished to
* do so, all subject to the following:
*
* The copyright notices in the Software and this entire statement, including
* the above license grant, this restriction and the following disclaimer,
* must be included in all copies of the Software, in whole or in part, and
* all derivative works of the Software, unless such copies or derivative
* works are solely in the form of machine-executable object code generated by
* a source language processor.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
* SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
* FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* ===========================================================================
*
* Author (editor, really):  Greg Boratyn, NCBI
*
* File Description:
*   Error function and complementary error function for normal distribution.
*   
* ===========================================================================
*/

#include "boost_erf.h"
#include <math.h>

#define FALSE 0
#define TRUE 1

static double ErfImpl(double z, int invert)
{
    if (z < 0) {
        if (!invert) {
            return -ErfImpl(-z, invert);
        }
        else if (z < -0.5) {
            return 2.0 - ErfImpl(-z, invert);
        }
        else {
            return 1 + ErfImpl(-z, FALSE);
        }
    }

    double result;

    /* Big bunch of selection statements now to pick
       which implementation to use,
       try to put most likely options first: */
    if (z < 0.5) {

        /* We're going to calculate erf: */
        if (z < 1e-10) {
            if (z == 0) {
                result = 0.0;
            }
            else {
                static const double c = 0.003379167095512573896158903121545171688;
                result = z * 1.125 + z * c;
            }
        }
        else {

            /* Maximum Deviation Found:                     1.561e-17
               Expected Error Term:                         1.561e-17
               Maximum Relative Change in Control Points:   1.155e-04
               Max Error found at double precision =        2.961182e-17 */

            static const double Y = 1.044948577880859375;
            static const double P[] = {0.0834305892146531832907,
                                       -0.338165134459360935041,
                                       -0.0509990735146777432841,
                                       -0.00772758345802133288487,
                                       -0.000322780120964605683831};

            static const double Q[] = {1.0,
                                       0.455004033050794024546,
                                       0.0875222600142252549554,
                                       0.00858571925074406212772,
                                       0.000370900071787748000569};

            double zz = z * z;

            double p = (((P[4] * zz + P[3]) * zz + P[2]) * zz + P[1]) * zz + P[0];
            double q = (((Q[4] * zz + Q[3]) * zz + Q[2]) * zz + Q[1]) * zz + Q[0];

            result = z * (Y + p / q);
        }
    }
    else if (invert ? (z < 28.0) : (z < 5.8)) {

        /* We'll be calculating erfc: */
        invert = !invert;

        if(z < 1.5) {

            /* Maximum Deviation Found:                     3.702e-17
               Expected Error Term:                         3.702e-17
               Maximum Relative Change in Control Points:   2.845e-04
               Max Error found at double precision =        4.841816e-17 */
            static const double Y = 0.405935764312744140625;
            static const double P[] = {-0.098090592216281240205,
                                       0.178114665841120341155,
                                       0.191003695796775433986,
                                       0.0888900368967884466578,
                                       0.0195049001251218801359,
                                       0.00180424538297014223957};

            static const double Q[] = {1.0,
                                  1.84759070983002217845,
                                  1.42628004845511324508,
                                  0.578052804889902404909,
                                  0.12385097467900864233,
                                  0.0113385233577001411017,
                                  0.337511472483094676155e-5};

            double x = z - 0.5;
            double p = ((((P[5] * x + P[4]) * x + P[3]) * x + P[2]) * x + P[1]) * x + P[0];
            double q = ((((Q[5] * x + Q[4]) * x + Q[3]) * x + Q[2]) * x + Q[1]) * x + Q[0];
            
            result = Y + p / q;
            result *= expl(-z * z) / z;
        }
        else if(z < 2.5) {

            /* Max Error found at double precision =        6.599585e-18
               Maximum Deviation Found:                     3.909e-18
               Expected Error Term:                         3.909e-18
               Maximum Relative Change in Control Points:   9.886e-05 */
            static const double Y = 0.50672817230224609375;
            static const double P[] = {-0.0243500476207698441272,
                                       0.0386540375035707201728,
                                       0.04394818964209516296,
                                       0.0175679436311802092299,
                                       0.00323962406290842133584,
                                       0.000235839115596880717416};

            static const double Q[] = {1.0,
                                       1.53991494948552447182,
                                       0.982403709157920235114,
                                       0.325732924782444448493,
                                       0.0563921837420478160373,
                                       0.00410369723978904575884};

            double x = z - 1.5;
            double p = ((((P[5] * x + P[4]) * x + P[3]) * x + P[2]) * x + P[1]) * x + P[0];
            double q = ((((Q[5] * x + Q[4]) * x + Q[3]) * x + Q[2]) * x + Q[1]) * x + Q[0];
          
            result = Y + p / q;
            result *= expl(-z * z) / z;
        }
        else if(z < 4.5) {

            /* Maximum Deviation Found:                     1.512e-17
               Expected Error Term:                         1.512e-17
               Maximum Relative Change in Control Points:   2.222e-04
               Max Error found at double precision =        2.062515e-17 */
            static const double Y = 0.5405750274658203125;
            static const double P[] = {0.00295276716530971662634,
                                       0.0137384425896355332126,
                                       0.00840807615555585383007,
                                       0.00212825620914618649141,
                                       0.000250269961544794627958,
                                       0.113212406648847561139e-4};

            static const double Q[] = {1.0,
                                       1.04217814166938418171,
                                       0.442597659481563127003,
                                       0.0958492726301061423444,
                                       0.0105982906484876531489,
                                       0.000479411269521714493907};

            double x = z - 3.5;
            double p = ((((P[5] * x + P[4]) * x + P[3]) * x + P[2]) * x + P[1]) * x + P[0];
            double q = ((((Q[5] * x + Q[4]) * x + Q[3]) * x + Q[2]) * x + Q[1]) * x + Q[0];

            result = Y + p / q;
            result *= expl(-z * z) / z;
        }
        else {

            /* Max Error found at double precision =        2.997958e-17
               Maximum Deviation Found:                     2.860e-17
               Expected Error Term:                         2.859e-17
               Maximum Relative Change in Control Points:   1.357e-05 */
            static const double Y = 0.5579090118408203125;
            static const double P[] = {0.00628057170626964891937,
                                       0.0175389834052493308818,
                                       -0.212652252872804219852,
                                       -0.687717681153649930619,
                                       -2.5518551727311523996,
                                       -3.22729451764143718517,
                                       -2.8175401114513378771};

            static const double Q[] = {1.0,
                                       2.79257750980575282228,
                                       11.0567237927800161565,
                                       15.930646027911794143,
                                       22.9367376522880577224,
                                       13.5064170191802889145,
                                       5.48409182238641741584};

            double x = 1.0 / z;
            double p = (((((P[6] * x + P[5]) * x + P[4]) * x + P[3]) * x + P[2]) * x + P[1]) * x + P[0];
            double q = (((((Q[6] * x + Q[5]) * x + Q[4]) * x + Q[3]) * x + Q[2]) * x + Q[1]) * x + Q[0];

            result = Y + p / q;
            result *= expl(-z * z) / z;
        }
    }
    else {

        /* Any value of z larger than 28 will underflow to zero: */
        result = 0;
        invert = !invert;
    }

    if(invert) {
        result = 1 - result;
    }

    return result;
}


double Erf(double z)
{
    return ErfImpl(z, FALSE);
}

double ErfC(double z)
{
    return ErfImpl(z, TRUE);
}

