Coverage Report

Created: 2026-09-28 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/math/distributions/normaldistribution.hpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2002, 2003 Ferdinando Ametrano
5
 Copyright (C) 2000, 2001, 2002, 2003 RiskMap srl
6
 Copyright (C) 2010 Kakhkhor Abdijalilov
7
8
 This file is part of QuantLib, a free-software/open-source library
9
 for financial quantitative analysts and developers - http://quantlib.org/
10
11
 QuantLib is free software: you can redistribute it and/or modify it
12
 under the terms of the QuantLib license.  You should have received a
13
 copy of the license along with this program; if not, please email
14
 <quantlib-dev@lists.sf.net>. The license is also available online at
15
 <https://www.quantlib.org/license.shtml>.
16
17
 This program is distributed in the hope that it will be useful, but WITHOUT
18
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19
 FOR A PARTICULAR PURPOSE.  See the license for more details.
20
*/
21
22
/*! \file normaldistribution.hpp
23
    \brief normal, cumulative and inverse cumulative distributions
24
*/
25
26
#ifndef quantlib_normal_distribution_hpp
27
#define quantlib_normal_distribution_hpp
28
29
#include <ql/math/errorfunction.hpp>
30
#include <ql/errors.hpp>
31
32
namespace QuantLib {
33
34
    //! Normal distribution function
35
    /*! Given x, it returns its probability in a Gaussian normal distribution.
36
        It provides the first derivative too.
37
38
      For average \f$ \mu \f$ and standard deviation \f$ \sigma \f$, the
39
      density is
40
      \f[
41
        f(x) = \frac{1}{\sigma\sqrt{2\pi}}
42
             \exp\left(-\frac{(x-\mu)^2}{2\sigma^2}\right).
43
      \f]
44
      The standard deviation must be strictly positive.
45
46
        \test the correctness of the returned value is tested by
47
              checking it against numerical calculations. Cross-checks
48
              are also performed against the
49
              CumulativeNormalDistribution and InverseCumulativeNormal
50
              classes.
51
    */
52
    class NormalDistribution {
53
      public:
54
        // TODO: Review whether this constructor should remain implicit.
55
        NormalDistribution(Real average = 0.0,
56
                           Real sigma = 1.0);
57
        // function
58
        Real operator()(Real x) const;
59
        Real derivative(Real x) const;
60
      private:
61
        Real average_, sigma_, normalizationFactor_, denominator_,
62
            derNormalizationFactor_;
63
    };
64
65
    typedef NormalDistribution GaussianDistribution;
66
67
68
    //! Cumulative normal distribution function
69
    /*! Given x, it provides an approximation to the cumulative probability
70
      of a Gaussian normal distribution with average \f$ \mu \f$ and
71
      standard deviation \f$ \sigma \f$:
72
      \f[
73
        F(x) = \frac{1}{\sigma\sqrt{2\pi}}
74
             \int_{-\infty}^{x}
75
             \exp\left(-\frac{(t-\mu)^2}{2\sigma^2}\right)dt.
76
      \f]
77
      The result is between zero and one, and derivative() returns the
78
      corresponding normal density. The lower tail uses an asymptotic
79
      expansion when the direct error-function calculation loses precision.
80
81
        For this implementation see M. Abramowitz and I. Stegun,
82
        Handbook of Mathematical Functions,
83
        Dover Publications, New York (1972)
84
    */
85
    class CumulativeNormalDistribution {
86
      public:
87
        // TODO: Review whether this constructor should remain implicit.
88
        CumulativeNormalDistribution(Real average = 0.0,
89
                                     Real sigma   = 1.0);
90
        // function
91
        Real operator()(Real x) const;
92
        Real derivative(Real x) const;
93
      private:
94
        Real average_, sigma_;
95
        NormalDistribution gaussian_;
96
        ErrorFunction errorFunction_;
97
    };
98
99
100
    //! Inverse cumulative normal distribution function
101
    /*! Given a probability \f$ p \f$ between zero and one, this class
102
      provides \f$ y = F^{-1}(p) \f$ such that
103
      \f[
104
          F(y) = p,
105
      \f]
106
      where \f$ F \f$ is the cumulative normal distribution.
107
108
      It uses Acklam's approximation:
109
      by Peter J. Acklam, University of Oslo, Statistics Division.
110
      URL: http://home.online.no/~pjacklam/notes/invnorm/index.html
111
112
      This class can also be used to generate a gaussian normal
113
      distribution from a uniform distribution.
114
      This is especially useful when a gaussian normal distribution
115
      is generated from a low discrepancy uniform distribution:
116
      in this case the traditional Box-Muller approach and its
117
      variants would not preserve the sequence's low-discrepancy.
118
119
    */
120
    class InverseCumulativeNormal {
121
      public:
122
        // TODO: Review whether this constructor should remain implicit.
123
        InverseCumulativeNormal(Real average = 0.0,
124
                                Real sigma   = 1.0);
125
        // function
126
2.13M
        Real operator()(Real x) const {
127
2.13M
            return average_ + sigma_*standard_value(x);
128
2.13M
        }
129
        // value for average=0, sigma=1
130
        /* Compared to operator(), this method avoids 2 floating point
131
           operations (we use average=0 and sigma=1 most of the
132
           time). The speed difference is noticeable.
133
        */
134
2.13M
        static Real standard_value(Real x) {
135
2.13M
            Real z;
136
2.13M
            if (x < x_low_ || x_high_ < x) {
137
106k
                z = tail_value(x);
138
2.02M
            } else {
139
2.02M
                z = x - 0.5;
140
2.02M
                Real r = z*z;
141
2.02M
                z = (((((a1_*r+a2_)*r+a3_)*r+a4_)*r+a5_)*r+a6_)*z /
142
2.02M
                    (((((b1_*r+b2_)*r+b3_)*r+b4_)*r+b5_)*r+1.0);
143
2.02M
            }
144
145
            // The relative error of the approximation has absolute value less
146
            // than 1.15e-9.  One iteration of Halley's rational method (third
147
            // order) gives full machine precision.
148
            // #define REFINE_TO_FULL_MACHINE_PRECISION_USING_HALLEYS_METHOD
149
            #ifdef REFINE_TO_FULL_MACHINE_PRECISION_USING_HALLEYS_METHOD
150
            // error (f_(z) - x) divided by the cumulative's derivative
151
            const Real r = (f_(z) - x) * M_SQRT2 * M_SQRTPI * exp(0.5 * z*z);
152
            //  Halley's method
153
            z -= r/(1+0.5*z*r);
154
            #endif
155
156
2.13M
            return z;
157
2.13M
        }
158
      private:
159
        /* Handling tails moved into a separate method, which should
160
           make the inlining of operator() and standard_value method
161
           easier. tail_value is called rarely and doesn't need to be
162
           inlined.
163
        */
164
        static Real tail_value(Real x);
165
        #if defined(QL_PATCH_SOLARIS)
166
        CumulativeNormalDistribution f_;
167
        #else
168
        static const CumulativeNormalDistribution f_;
169
        #endif
170
        Real average_, sigma_;
171
        static const Real a1_;
172
        static const Real a2_;
173
        static const Real a3_;
174
        static const Real a4_;
175
        static const Real a5_;
176
        static const Real a6_;
177
        static const Real b1_;
178
        static const Real b2_;
179
        static const Real b3_;
180
        static const Real b4_;
181
        static const Real b5_;
182
        static const Real c1_;
183
        static const Real c2_;
184
        static const Real c3_;
185
        static const Real c4_;
186
        static const Real c5_;
187
        static const Real c6_;
188
        static const Real d1_;
189
        static const Real d2_;
190
        static const Real d3_;
191
        static const Real d4_;
192
        static const Real x_low_;
193
        static const Real x_high_;
194
    };
195
196
    // backward compatibility
197
    typedef InverseCumulativeNormal InvCumulativeNormalDistribution;
198
199
    //! Moro Inverse cumulative normal distribution class
200
    /*! Given x between zero and one as
201
        the integral value of a gaussian normal distribution
202
        this class provides the value y such that
203
        formula here ...
204
205
        It uses the Beasley-Springer approximation in the central region,
206
        with an improved Moro approximation for the tails. See Boris Moro,
207
        "The Full Monte", 1995, Risk Magazine.
208
209
        This class can also be used to generate a gaussian normal
210
        distribution from a uniform distribution.
211
        This is especially useful when a gaussian normal distribution
212
        is generated from a low discrepancy uniform distribution:
213
        in this case the traditional Box-Muller approach and its
214
        variants would not preserve the sequence's low-discrepancy.
215
216
        Peter J. Acklam's approximation is better and is available
217
        as QuantLib::InverseCumulativeNormal
218
    */
219
    class MoroInverseCumulativeNormal {
220
      public:
221
        // TODO: Review whether this constructor should remain implicit.
222
        MoroInverseCumulativeNormal(Real average = 0.0,
223
                                    Real sigma   = 1.0);
224
        // function
225
        Real operator()(Real x) const;
226
      private:
227
        Real average_, sigma_;
228
        static const Real a0_;
229
        static const Real a1_;
230
        static const Real a2_;
231
        static const Real a3_;
232
        static const Real b0_;
233
        static const Real b1_;
234
        static const Real b2_;
235
        static const Real b3_;
236
        static const Real c0_;
237
        static const Real c1_;
238
        static const Real c2_;
239
        static const Real c3_;
240
        static const Real c4_;
241
        static const Real c5_;
242
        static const Real c6_;
243
        static const Real c7_;
244
        static const Real c8_;
245
    };
246
247
    //! Maddock's Inverse cumulative normal distribution class
248
    /*! Given a probability \f$ p \f$ between zero and one, this class
249
      provides \f$ y = F^{-1}(p) \f$ such that
250
      \f[
251
        F(y) = p,
252
      \f]
253
      where \f$ F \f$ is the cumulative normal distribution.
254
255
        From the boost documentation:
256
         These functions use a rational approximation devised by
257
         John Maddock to calculate an initial approximation to the
258
         result that is accurate to ~10^-19, then only if that has
259
         insufficient accuracy compared to the epsilon for type double,
260
         do we clean up the result using Halley iteration.
261
    */
262
    class MaddockInverseCumulativeNormal {
263
      public:
264
        // TODO: Review whether this constructor should remain implicit.
265
        MaddockInverseCumulativeNormal(Real average = 0.0,
266
                                       Real sigma   = 1.0);
267
        Real operator()(Real x) const;
268
269
      private:
270
        const Real average_, sigma_;
271
    };
272
273
    //! Maddock's cumulative normal distribution class
274
    class MaddockCumulativeNormal {
275
      public:
276
        // TODO: Review whether this constructor should remain implicit.
277
        MaddockCumulativeNormal(Real average = 0.0,
278
                                       Real sigma   = 1.0);
279
        Real operator()(Real x) const;
280
281
      private:
282
        const Real average_, sigma_;
283
    };
284
285
286
    // inline definitions
287
288
    inline NormalDistribution::NormalDistribution(Real average,
289
                                                  Real sigma)
290
24.2k
    : average_(average), sigma_(sigma) {
291
292
24.2k
        QL_REQUIRE(sigma_>0.0,
293
24.2k
                   "sigma must be greater than 0.0 ("
294
24.2k
                   << sigma_ << " not allowed)");
295
296
24.2k
        normalizationFactor_ = M_SQRT_2*M_1_SQRTPI/sigma_;
297
24.2k
        derNormalizationFactor_ = sigma_*sigma_;
298
24.2k
        denominator_ = 2.0*derNormalizationFactor_;
299
24.2k
    }
300
301
19.8k
    inline Real NormalDistribution::operator()(Real x) const {
302
19.8k
        Real deltax = x-average_;
303
19.8k
        Real exponent = -(deltax*deltax)/denominator_;
304
        // debian alpha had some strange problem in the very-low range
305
19.8k
        return exponent <= -690.0 ? 0.0 :  // exp(x) < 1.0e-300 anyway
306
19.8k
            Real(normalizationFactor_*std::exp(exponent));
307
19.8k
    }
308
309
305
    inline Real NormalDistribution::derivative(Real x) const {
310
305
        const Real density = (*this)(x);
311
305
        return density == 0.0 ? 0.0 :
312
305
            (density * (average_ - x)) / derNormalizationFactor_;
313
305
    }
314
315
    inline CumulativeNormalDistribution::CumulativeNormalDistribution(
316
                                                 Real average, Real sigma)
317
23.3k
    : average_(average), sigma_(sigma) {
318
319
23.3k
        QL_REQUIRE(sigma_>0.0,
320
23.3k
                   "sigma must be greater than 0.0 ("
321
23.3k
                   << sigma_ << " not allowed)");
322
23.3k
    }
323
324
7.74k
    inline Real CumulativeNormalDistribution::derivative(Real x) const {
325
7.74k
        Real xn = (x - average_) / sigma_;
326
7.74k
        return gaussian_(xn) / sigma_;
327
7.74k
    }
328
329
    inline InverseCumulativeNormal::InverseCumulativeNormal(
330
                                                 Real average, Real sigma)
331
3.84k
    : average_(average), sigma_(sigma) {
332
333
        QL_REQUIRE(sigma_>0.0,
334
3.84k
                   "sigma must be greater than 0.0 ("
335
3.84k
                   << sigma_ << " not allowed)");
336
3.84k
    }
337
338
    inline MoroInverseCumulativeNormal::MoroInverseCumulativeNormal(
339
                                                 Real average, Real sigma)
340
    : average_(average), sigma_(sigma) {
341
342
        QL_REQUIRE(sigma_>0.0,
343
                   "sigma must be greater than 0.0 ("
344
                   << sigma_ << " not allowed)");
345
    }
346
347
}
348
349
350
#endif