Coverage Report

Created: 2026-03-20 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/pxfm-0.1.28/src/err/erf.rs
Line
Count
Source
1
/*
2
 * // Copyright (c) Radzivon Bartoshyk 7/2025. All rights reserved.
3
 * //
4
 * // Redistribution and use in source and binary forms, with or without modification,
5
 * // are permitted provided that the following conditions are met:
6
 * //
7
 * // 1.  Redistributions of source code must retain the above copyright notice, this
8
 * // list of conditions and the following disclaimer.
9
 * //
10
 * // 2.  Redistributions in binary form must reproduce the above copyright notice,
11
 * // this list of conditions and the following disclaimer in the documentation
12
 * // and/or other materials provided with the distribution.
13
 * //
14
 * // 3.  Neither the name of the copyright holder nor the names of its
15
 * // contributors may be used to endorse or promote products derived from
16
 * // this software without specific prior written permission.
17
 * //
18
 * // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
 * // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
 * // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
 * // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
 * // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
 * // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
 * // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
 * // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
 * // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 */
29
use crate::common::{dd_fmla, dyad_fmla, f_fmla};
30
use crate::double_double::DoubleDouble;
31
use crate::err::erf_poly::{ERF_POLY, ERF_POLY_C2};
32
use crate::rounding::CpuFloor;
33
/* double-double approximation of 2/sqrt(pi) to nearest */
34
const TWO_OVER_SQRT_PI: DoubleDouble = DoubleDouble::new(
35
    f64::from_bits(0x3c71ae3a914fed80),
36
    f64::from_bits(0x3ff20dd750429b6d),
37
);
38
39
pub(crate) struct Erf {
40
    pub(crate) result: DoubleDouble,
41
    pub(crate) err: f64,
42
}
43
44
/* for |z| < 1/8, assuming z >= 2^-61, thus no underflow can occur */
45
#[cold]
46
0
fn cr_erf_accurate_tiny(x: f64) -> DoubleDouble {
47
    static P: [u64; 15] = [
48
        0x3ff20dd750429b6d,
49
        0x3c71ae3a914fed80,
50
        0xbfd812746b0379e7,
51
        0x3c6ee12e49ca96ba,
52
        0x3fbce2f21a042be2,
53
        0xbc52871bc0a0a0d0,
54
        0xbf9b82ce31288b51,
55
        0x3c21003accf1355c,
56
        0x3f7565bcd0e6a53f,
57
        0xbf4c02db40040cc3,
58
        0x3f1f9a326fa3cf50,
59
        0xbeef4d25e3c73ce9,
60
        0x3ebb9eb332b31646,
61
        0xbe864a4bd5eca4d7,
62
        0x3e6c0acc2502e94e,
63
    ];
64
0
    let z2 = x * x;
65
0
    let mut h = f64::from_bits(P[21 / 2 + 4]); /* degree 21 */
66
0
    for a in (12..=19).rev().step_by(2) {
67
0
        h = dd_fmla(h, z2, f64::from_bits(P[(a / 2 + 4) as usize]))
68
    }
69
0
    let mut l = 0.;
70
0
    for a in (8..=11).rev().step_by(2) {
71
0
        let mut t = DoubleDouble::from_exact_mult(h, x);
72
0
        t.lo = dd_fmla(l, x, t.lo);
73
0
        let mut k = DoubleDouble::from_exact_mult(t.hi, x);
74
0
        k.lo = dd_fmla(t.lo, x, k.lo);
75
0
        let p = DoubleDouble::from_exact_add(f64::from_bits(P[(a / 2 + 4) as usize]), k.hi);
76
0
        l = k.lo + p.lo;
77
0
        h = p.hi;
78
0
    }
79
0
    for a in (1..=7).rev().step_by(2) {
80
0
        let mut t = DoubleDouble::from_exact_mult(h, x);
81
0
        t.lo = dd_fmla(l, x, t.lo);
82
0
        let mut k = DoubleDouble::from_exact_mult(t.hi, x);
83
0
        k.lo = dd_fmla(t.lo, x, k.lo);
84
0
        let p = DoubleDouble::from_exact_add(f64::from_bits(P[a - 1]), k.hi);
85
0
        l = k.lo + p.lo + f64::from_bits(P[a]);
86
0
        h = p.hi;
87
0
    }
88
    /* multiply by z */
89
0
    let p = DoubleDouble::from_exact_mult(h, x);
90
0
    l = dd_fmla(l, x, p.lo);
91
0
    DoubleDouble::new(l, p.hi)
92
0
}
93
94
/* Assuming 0 <= z <= 0x1.7afb48dc96626p+2, put in h+l an accurate
95
approximation of erf(z).
96
Assumes z >= 2^-61, thus no underflow can occur. */
97
#[cold]
98
#[inline(never)]
99
0
pub(crate) fn erf_accurate(x: f64) -> DoubleDouble {
100
0
    if x < 0.125
101
    /* z < 1/8 */
102
    {
103
0
        return cr_erf_accurate_tiny(x);
104
0
    }
105
106
0
    let v = (8.0 * x).cpu_floor();
107
0
    let i: u32 = (8.0 * x) as u32;
108
0
    let z = (x - 0.0625) - 0.125 * v;
109
    /* now |z| <= 1/16 */
110
0
    let p = ERF_POLY_C2[(i - 1) as usize];
111
0
    let mut h = f64::from_bits(p[26]); /* degree-18 */
112
0
    for a in (11..=17).rev() {
113
0
        h = dd_fmla(h, z, f64::from_bits(p[(8 + a) as usize])); /* degree j */
114
0
    }
115
0
    let mut l: f64 = 0.;
116
0
    for a in (8..=10).rev() {
117
0
        let mut t = DoubleDouble::from_exact_mult(h, z);
118
0
        t.lo = dd_fmla(l, z, t.lo);
119
0
        let p = DoubleDouble::from_exact_add(f64::from_bits(p[(8 + a) as usize]), t.hi);
120
0
        h = p.hi;
121
0
        l = p.lo + t.lo;
122
0
    }
123
0
    for a in (0..=7).rev() {
124
0
        let mut t = DoubleDouble::from_exact_mult(h, z);
125
0
        t.lo = dd_fmla(l, z, t.lo);
126
0
        /* add p[2*j] + p[2*j+1] to th + tl: we use two_sum() instead of
127
0
        fast_two_sum because for example for i=3, the coefficient of
128
0
        degree 7 is tiny (0x1.060b78c935b8ep-13) with respect to that
129
0
        of degree 8 (0x1.678b51a9c4b0ap-7) */
130
0
        let v = DoubleDouble::from_exact_add(f64::from_bits(p[(2 * a) as usize]), t.hi);
131
0
        h = v.hi;
132
0
        l = v.lo + t.lo + f64::from_bits(p[(2 * a + 1) as usize]);
133
0
    }
134
0
    DoubleDouble::new(l, h)
135
0
}
136
137
/* Assuming 0 <= z <= 5.9215871957945065, put in h+l an approximation
138
of erf(z). Return err the maximal relative error:
139
|(h + l)/erf(z) - 1| < err*|h+l| */
140
#[inline]
141
0
pub(crate) fn erf_fast(x: f64) -> Erf {
142
    /* we split [0,5.9215871957945065] into intervals i/16 <= z < (i+1)/16,
143
       and for each interval, we use a minimax polynomial:
144
       * for i=0 (0 <= z < 1/16) we use a polynomial evaluated at zero,
145
         since if we evaluate in the middle 1/32, we will get bad accuracy
146
         for tiny z, and moreover z-1/32 might not be exact
147
       * for 1 <= i <= 94, we use a polynomial evaluated in the middle of
148
         the interval, namely i/16+1/32
149
    */
150
0
    if x < 0.0625
151
    /* z < 1/16 */
152
    {
153
        /* the following is a degree-11 minimax polynomial for erf(x) on [0,1/16]
154
        generated by Sollya, with double-double coefficients for degree 1 and 3,
155
        and double coefficients for degrees 5 to 11 (file erf0.sollya).
156
        The maximal relative error is 2^-68.935. */
157
0
        let z2 = DoubleDouble::from_exact_mult(x, x);
158
        const C: [u64; 8] = [
159
            0x3ff20dd750429b6d,
160
            0x3c71ae3a7862d9c4,
161
            0xbfd812746b0379e7,
162
            0x3c6f1a64d72722a2,
163
            0x3fbce2f21a042b7f,
164
            0xbf9b82ce31189904,
165
            0x3f7565bbf8a0fe0b,
166
            0xbf4bf9f8d2c202e4,
167
        ];
168
0
        let z4 = z2.hi * z2.hi;
169
0
        let c9 = dd_fmla(f64::from_bits(C[7]), z2.hi, f64::from_bits(C[6]));
170
0
        let mut c5 = dd_fmla(f64::from_bits(C[5]), z2.hi, f64::from_bits(C[4]));
171
0
        c5 = dd_fmla(c9, z4, c5);
172
        /* compute c0[2] + c0[3] + z2h*c5 */
173
0
        let mut t = DoubleDouble::from_exact_mult(z2.hi, c5);
174
0
        let mut v = DoubleDouble::from_exact_add(f64::from_bits(C[2]), t.hi);
175
0
        v.lo += t.lo + f64::from_bits(C[3]);
176
        /* compute c0[0] + c0[1] + (z2h + z2l)*(h + l) */
177
0
        t = DoubleDouble::from_exact_mult(z2.hi, v.hi);
178
0
        let h_c = v.hi;
179
0
        t.lo += dd_fmla(z2.hi, v.lo, f64::from_bits(C[1]));
180
0
        v = DoubleDouble::from_exact_add(f64::from_bits(C[0]), t.hi);
181
0
        v.lo += dd_fmla(z2.lo, h_c, t.lo);
182
0
        v = DoubleDouble::quick_mult_f64(v, x);
183
0
        return Erf {
184
0
            result: v,
185
0
            err: f64::from_bits(0x3ba7800000000000),
186
0
        }; /* err < 2.48658249618372e-21, cf Analyze0() */
187
0
    }
188
189
0
    let v = (16.0 * x).cpu_floor();
190
0
    let i: u32 = (16.0 * x) as u32;
191
    /* i/16 <= z < (i+1)/16 */
192
    /* For 0.0625 0 <= z <= 0x1.7afb48dc96626p+2, z - 0.03125 is exact:
193
    (1) either z - 0.03125 is in the same binade as z, then 0.03125 is
194
        an integer multiple of ulp(z), so is z - 0.03125
195
    (2) if z - 0.03125 is in a smaller binade, both z and 0.03125 are
196
        integer multiple of the ulp() of that smaller binade.
197
    Also, subtracting 0.0625 * v is exact. */
198
0
    let z = (x - 0.03125) - 0.0625 * v;
199
    /* now |z| <= 1/32 */
200
0
    let c = ERF_POLY[(i - 1) as usize];
201
0
    let z2 = z * z;
202
0
    let z4 = z2 * z2;
203
    /* the degree-10 coefficient is c[12] */
204
0
    let c9 = dd_fmla(f64::from_bits(c[12]), z, f64::from_bits(c[11]));
205
0
    let mut c7 = dd_fmla(f64::from_bits(c[10]), z, f64::from_bits(c[9]));
206
0
    let c5 = dd_fmla(f64::from_bits(c[8]), z, f64::from_bits(c[7]));
207
    /* c3h, c3l <- c[5] + z*c[6] */
208
0
    let mut c3 = DoubleDouble::from_exact_add(f64::from_bits(c[5]), z * f64::from_bits(c[6]));
209
0
    c7 = dd_fmla(c9, z2, c7);
210
    /* c3h, c3l <- c3h, c3l + c5*z2 */
211
0
    let p = DoubleDouble::from_exact_add(c3.hi, c5 * z2);
212
0
    c3.hi = p.hi;
213
0
    c3.lo += p.lo;
214
    /* c3h, c3l <- c3h, c3l + c7*z4 */
215
0
    let p = DoubleDouble::from_exact_add(c3.hi, c7 * z4);
216
0
    c3.hi = p.hi;
217
0
    c3.lo += p.lo;
218
    /* c2h, c2l <- c[4] + z*(c3h + c3l) */
219
0
    let mut t = DoubleDouble::from_exact_mult(z, c3.hi);
220
0
    let mut c2 = DoubleDouble::from_exact_add(f64::from_bits(c[4]), t.hi);
221
0
    c2.lo += dd_fmla(z, c3.lo, t.lo);
222
    /* compute c[2] + c[3] + z*(c2h + c2l) */
223
0
    t = DoubleDouble::from_exact_mult(z, c2.hi);
224
0
    let mut v = DoubleDouble::from_exact_add(f64::from_bits(c[2]), t.hi);
225
0
    v.lo += t.lo + dd_fmla(z, c2.lo, f64::from_bits(c[3]));
226
    /* compute c[0] + c[1] + z*(h + l) */
227
0
    t = DoubleDouble::from_exact_mult(z, v.hi);
228
0
    t.lo = dd_fmla(z, v.lo, t.lo);
229
0
    v = DoubleDouble::from_exact_add(f64::from_bits(c[0]), t.hi);
230
0
    v.lo += t.lo + f64::from_bits(c[1]);
231
0
    Erf {
232
0
        result: v,
233
0
        err: f64::from_bits(0x3ba1100000000000),
234
0
    } /* err < 1.80414390200020e-21, cf analyze_p(1)
235
    (larger values of i yield smaller error bounds) */
236
0
}
237
238
/// Error function
239
///
240
/// Max ULP 0.5
241
0
pub fn f_erf(x: f64) -> f64 {
242
0
    let z = f64::from_bits(x.to_bits() & 0x7fff_ffff_ffff_ffff);
243
0
    let mut t = z.to_bits();
244
0
    let ux = t;
245
    /* erf(x) rounds to +/-1 for RNDN for |x| > 0x4017afb48dc96626 */
246
0
    if ux > 0x4017afb48dc96626
247
    // |x| > 0x4017afb48dc96626
248
    {
249
0
        let os = f64::copysign(1.0, x);
250
        const MASK: u64 = 0x7ff0000000000000u64;
251
0
        if ux > MASK {
252
0
            return x + x; /* NaN */
253
0
        }
254
0
        if ux == MASK {
255
0
            return os; /* +/-Inf */
256
0
        }
257
0
        return f_fmla(-f64::from_bits(0x3c90000000000000), os, os);
258
0
    }
259
260
    /* now |x| <= 0x4017afb48dc96626 */
261
0
    if z < f64::from_bits(0x3c20000000000000) {
262
        /* for x=-0 the code below returns +0 which is wrong */
263
0
        if x == 0. {
264
0
            return x;
265
0
        }
266
        /* tiny x: erf(x) ~ 2/sqrt(pi) * x + O(x^3), where the ratio of the O(x^3)
267
        term to the main term is in x^2/3, thus less than 2^-123 */
268
0
        let y = TWO_OVER_SQRT_PI.hi * x; /* tentative result */
269
        /* scale x by 2^106 to get out the subnormal range */
270
0
        let sx = x * f64::from_bits(0x4690000000000000);
271
0
        let mut p = DoubleDouble::quick_mult_f64(TWO_OVER_SQRT_PI, sx);
272
        /* now compute the residual h + l - y */
273
0
        p.lo += f_fmla(-y, f64::from_bits(0x4690000000000000), p.hi); /* h-y*2^106 is exact since h and y are very close */
274
0
        let res = dyad_fmla(p.lo, f64::from_bits(0x3950000000000000), y);
275
0
        return res;
276
0
    }
277
278
0
    let result = erf_fast(z);
279
0
    let mut u = result.result.hi.to_bits();
280
0
    let mut v = result.result.lo.to_bits();
281
0
    t = x.to_bits();
282
283
    const SIGN_MASK: u64 = 0x8000000000000000u64;
284
0
    u ^= t & SIGN_MASK;
285
0
    v ^= t & SIGN_MASK;
286
287
0
    let left = f64::from_bits(u) + f_fmla(result.err, -f64::from_bits(u), f64::from_bits(v));
288
0
    let right = f64::from_bits(u) + f_fmla(result.err, f64::from_bits(u), f64::from_bits(v));
289
290
0
    if left == right {
291
0
        return left;
292
0
    }
293
294
0
    let a_results = erf_accurate(z);
295
296
0
    if x >= 0. {
297
0
        a_results.to_f64()
298
    } else {
299
0
        (-a_results.hi) + (-a_results.lo)
300
    }
301
0
}
302
303
#[cfg(test)]
304
mod tests {
305
    use super::*;
306
307
    #[test]
308
    fn test_erf() {
309
        assert_eq!(f_erf(0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009456563898732),
310
                   0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010670589695636709);
311
        assert_eq!(f_erf(0.), 0.);
312
        assert_eq!(f_erf(1.), 0.8427007929497149);
313
        assert_eq!(f_erf(0.49866735123), 0.5193279892991808);
314
        assert_eq!(f_erf(-0.49866735123), -0.5193279892991808);
315
        assert!(f_erf(f64::NAN).is_nan());
316
        assert_eq!(f_erf(f64::INFINITY), 1.0);
317
        assert_eq!(f_erf(f64::NEG_INFINITY), -1.0);
318
    }
319
}