pxfm/compound/
compound_d.rs

1/*
2 * // Copyright (c) Radzivon Bartoshyk 8/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 */
29use crate::common::{f_fmla, is_integer, is_odd_integer};
30use crate::double_double::DoubleDouble;
31use crate::dyadic_float::{DyadicFloat128, DyadicSign};
32use crate::logs::{log1p_f64_dyadic, log1p_fast_dd};
33use crate::pow_exec::{exp_dyadic, pow_exp_dd};
34use crate::triple_double::TripleDouble;
35
36/// Computes (1+x)^y
37///
38pub fn f_compound(x: f64, y: f64) -> f64 {
39    /*
40       Rules from IEEE 754-2019 for compound (x, n) with n integer:
41           (a) compound (x, 0) is 1 for x >= -1 or quiet NaN
42           (b) compound (-1, n) is +Inf and signals the divideByZero exception for n < 0
43           (c) compound (-1, n) is +0 for n > 0
44           (d) compound (+/-0, n) is 1
45           (e) compound (+Inf, n) is +Inf for n > 0
46           (f) compound (+Inf, n) is +0 for n < 0
47           (g) compound (x, n) is qNaN and signals the invalid exception for x < -1
48           (h) compound (qNaN, n) is qNaN for n <> 0.
49    */
50
51    let x_sign = x.is_sign_negative();
52    let y_sign = y.is_sign_negative();
53
54    let x_abs = x.to_bits() & 0x7fff_ffff_ffff_ffff;
55    let y_abs = y.to_bits() & 0x7fff_ffff_ffff_ffff;
56
57    const MANTISSA_MASK: u64 = (1u64 << 52) - 1;
58
59    let y_mant = y.to_bits() & MANTISSA_MASK;
60    let x_u = x.to_bits();
61    let x_a = x_abs;
62    let y_a = y_abs;
63
64    // If x or y is signaling NaN
65    if x.is_nan() || y.is_nan() {
66        return f64::NAN;
67    }
68
69    let mut s = 1.0;
70
71    let ax = x.to_bits() & 0x7fff_ffff_ffff_ffff;
72    let ay = y.to_bits() & 0x7fff_ffff_ffff_ffff;
73
74    // The double precision number that is closest to 1 is (1 - 2^-53), which has
75    //   log2(1 - 2^-53) ~ -1.715...p-53.
76    // So if |y| > |1075 / log2(1 - 2^-53)|, and x is finite:
77    //   |y * log2(x)| = 0 or > 1075.
78    // Hence, x^y will either overflow or underflow if x is not zero.
79    if y_mant == 0
80        || y_a > 0x43d7_4910_d52d_3052
81        || x_u == 1f64.to_bits()
82        || x_u >= f64::INFINITY.to_bits()
83        || x_u < f64::MIN.to_bits()
84    {
85        // Exceptional exponents.
86        if y == 0.0 {
87            return 1.0;
88        }
89
90        // (h) compound(qNaN, n) is qNaN for n ≠ 0
91        if x.is_nan() {
92            if y != 0. {
93                return x;
94            } // propagate qNaN
95            return 1.0;
96        }
97
98        // (d) compound(±0, n) is 1
99        if x == 0.0 {
100            return 1.0;
101        }
102
103        // (e, f) compound(+Inf, n)
104        if x.is_infinite() && x > 0.0 {
105            return if y > 0. { x } else { 0.0 };
106        }
107
108        // (g) compound(x, n) is qNaN and signals invalid for x < -1
109        if x < -1.0 {
110            // Optional: raise invalid explicitly
111            return f64::NAN;
112        }
113
114        // (b, c) compound(-1, n)
115        if x == -1.0 {
116            return if y < 0. { f64::INFINITY } else { 0.0 };
117        }
118
119        match y_a {
120            0x3fe0_0000_0000_0000 => {
121                // TODO: speed up x^(-1/2) with rsqrt(x) when available.
122                if x == 0.0 {
123                    return 1.0;
124                }
125                let z = DoubleDouble::from_full_exact_add(x, 1.0).sqrt();
126                return if y_sign {
127                    z.recip().to_f64()
128                } else {
129                    z.to_f64()
130                };
131            }
132            0x3ff0_0000_0000_0000 => {
133                return if y_sign {
134                    const ONES: DyadicFloat128 = DyadicFloat128 {
135                        sign: DyadicSign::Pos,
136                        exponent: -127,
137                        mantissa: 0x80000000_00000000_00000000_00000000_u128,
138                    };
139                    let z = DyadicFloat128::new_from_f64(x) + ONES;
140                    z.reciprocal().fast_as_f64()
141                } else {
142                    DoubleDouble::from_full_exact_add(x, 1.0).to_f64()
143                };
144            }
145            0x4000_0000_0000_0000 => {
146                let z0 = DoubleDouble::from_full_exact_add(x, 1.0);
147                let z = DoubleDouble::quick_mult(z0, z0);
148                return if y_sign {
149                    z.recip().to_f64()
150                } else {
151                    f64::copysign(z.to_f64(), x)
152                };
153            }
154            _ => {}
155        }
156
157        // |y| > |1075 / log2(1 - 2^-53)|.
158        if y_a >= 0x7ff0_0000_0000_0000 {
159            // y is inf or nan
160            if y_mant != 0 {
161                // y is NaN
162                // pow(1, NaN) = 1
163                // pow(x, NaN) = NaN
164                return if x_u == 1f64.to_bits() { 1.0 } else { y };
165            }
166
167            // Now y is +-Inf
168            if f64::from_bits(x_abs).is_nan() {
169                // pow(NaN, +-Inf) = NaN
170                return x;
171            }
172
173            if x == 0.0 && y_sign {
174                // pow(+-0, -Inf) = +inf and raise FE_DIVBYZERO
175                return f64::INFINITY;
176            }
177            // pow (|x| < 1, -inf) = +inf
178            // pow (|x| < 1, +inf) = 0.0
179            // pow (|x| > 1, -inf) = 0.0
180            // pow (|x| > 1, +inf) = +inf
181            return if (x_a < 1f64.to_bits()) == y_sign {
182                f64::INFINITY
183            } else {
184                0.0
185            };
186        }
187
188        // y is finite and non-zero.
189
190        if x == 0.0 {
191            let out_is_neg = x_sign && is_odd_integer(y);
192            if y_sign {
193                // pow(0, negative number) = inf
194                return if out_is_neg {
195                    f64::NEG_INFINITY
196                } else {
197                    f64::INFINITY
198                };
199            }
200            // pow(0, positive number) = 0
201            return if out_is_neg { -0.0 } else { 0.0 };
202        }
203
204        if x_a == f64::INFINITY.to_bits() {
205            let out_is_neg = x_sign && is_odd_integer(y);
206            if y_sign {
207                return if out_is_neg { -0.0 } else { 0.0 };
208            }
209            return if out_is_neg {
210                f64::NEG_INFINITY
211            } else {
212                f64::INFINITY
213            };
214        }
215
216        if x_a > f64::INFINITY.to_bits() {
217            // x is NaN.
218            // pow (aNaN, 0) is already taken care above.
219            return x;
220        }
221
222        // x is finite and negative, and y is a finite integer.
223        if x_sign {
224            if is_integer(y) {
225                if is_odd_integer(y) {
226                    // sign = -1.0;
227                    static CS: [f64; 2] = [1.0, -1.0];
228
229                    // set sign to 1 for y even, to -1 for y odd
230                    let y_parity = if (y.abs()) >= f64::from_bits(0x4340000000000000) {
231                        0usize
232                    } else {
233                        (y as i64 & 0x1) as usize
234                    };
235                    s = CS[y_parity];
236                }
237            } else {
238                // pow( negative, non-integer ) = NaN
239                return f64::NAN;
240            }
241        }
242
243        // y is finite and non-zero.
244
245        if x_u == 1f64.to_bits() {
246            // compound(1, y) = 1
247            return 2.0;
248        }
249
250        if x == 0.0 {
251            let out_is_neg = x_sign && is_odd_integer(y);
252            if y_sign {
253                // pow(0, negative number) = inf
254                return if out_is_neg {
255                    f64::NEG_INFINITY
256                } else {
257                    f64::INFINITY
258                };
259            }
260            // pow(0, positive number) = 0
261            return if out_is_neg { -0.0 } else { 0.0 };
262        }
263
264        if x_a == f64::INFINITY.to_bits() {
265            let out_is_neg = x_sign && is_odd_integer(y);
266            if y_sign {
267                return if out_is_neg { -0.0 } else { 0.0 };
268            }
269            return if out_is_neg {
270                f64::NEG_INFINITY
271            } else {
272                f64::INFINITY
273            };
274        }
275
276        if x_a > f64::INFINITY.to_bits() {
277            // x is NaN.
278            // pow (aNaN, 0) is already taken care above.
279            return x;
280        }
281
282        let min_abs = f64::min(f64::from_bits(ax), f64::from_bits(ay)).to_bits();
283        let max_abs = f64::max(f64::from_bits(ax), f64::from_bits(ay)).to_bits();
284        let min_exp = min_abs.wrapping_shr(52);
285        let max_exp = max_abs.wrapping_shr(52);
286
287        if max_exp > 0x7ffu64 - 128u64 || min_exp < 128u64 {
288            let scale_up = min_exp < 128u64;
289            let scale_down = max_exp > 0x7ffu64 - 128u64;
290            // At least one input is denormal, multiply both numerator and denominator
291            // then will go with hard path
292            if scale_up || scale_down {
293                return compound_accurate(x, y, s);
294            }
295        }
296    }
297
298    #[cfg(any(
299        all(
300            any(target_arch = "x86", target_arch = "x86_64"),
301            target_feature = "fma"
302        ),
303        all(target_arch = "aarch64", target_feature = "neon")
304    ))]
305    let straight_path_precondition: bool = true;
306    #[cfg(not(any(
307        all(
308            any(target_arch = "x86", target_arch = "x86_64"),
309            target_feature = "fma"
310        ),
311        all(target_arch = "aarch64", target_feature = "neon")
312    )))]
313    let straight_path_precondition: bool = y.is_sign_positive();
314    // this is correct only for positive exponent number without FMA,
315    // otherwise reciprocal may overflow.
316    // y is integer and in [-102;102] and |x|<2^10
317    if is_integer(y)
318        && y_a <= 0x4059800000000000u64
319        && x_a <= 0x4090000000000000u64
320        && x_a > 0x3cc0_0000_0000_0000
321        && straight_path_precondition
322    {
323        let mut s = DoubleDouble::from_full_exact_add(1.0, x);
324        let mut iter_count = unsafe { y.abs().to_int_unchecked::<usize>() };
325
326        // exponentiation by squaring: O(log(y)) complexity
327        let mut acc = if iter_count % 2 != 0 {
328            s
329        } else {
330            DoubleDouble::new(0., 1.)
331        };
332
333        while {
334            iter_count >>= 1;
335            iter_count
336        } != 0
337        {
338            s = DoubleDouble::mult(s, s);
339            if iter_count % 2 != 0 {
340                acc = DoubleDouble::mult(acc, s);
341            }
342        }
343
344        let dz = if y.is_sign_negative() {
345            acc.recip()
346        } else {
347            acc
348        };
349        let ub = dz.hi + f_fmla(f64::from_bits(0x3c40000000000000), -dz.hi, dz.lo); // 2^-59
350        let lb = dz.hi + f_fmla(f64::from_bits(0x3c40000000000000), dz.hi, dz.lo); // 2^-59
351        if ub == lb {
352            return dz.to_f64();
353        }
354        return mul_fixed_power_hard(x, y);
355    }
356
357    let l = log1p_fast_dd(x);
358    let ey = ((y.to_bits() >> 52) & 0x7ff) as i32;
359    if ey < 0x36 || ey >= 0x7f5 {
360        return compound_accurate(x, y, s);
361    }
362
363    let r = DoubleDouble::quick_mult_f64(l, y);
364    let res = pow_exp_dd(r, s);
365    let res_min = res.hi + f_fmla(f64::from_bits(0x3bf0000000000000), -res.hi, res.lo);
366    let res_max = res.hi + f_fmla(f64::from_bits(0x3bf0000000000000), res.hi, res.lo);
367    if res_min == res_max {
368        return res_max;
369    }
370
371    compound_accurate(x, y, s)
372}
373
374#[cold]
375fn compound_accurate(x: f64, y: f64, s: f64) -> f64 {
376    /* the idea of returning res_max instead of res_min is due to Laurent
377    Théry: it is better in case of underflow since res_max = +0 always. */
378
379    let f_y = DyadicFloat128::new_from_f64(y);
380
381    let r = log1p_f64_dyadic(x) * f_y;
382    let mut result = exp_dyadic(r);
383
384    // 2^R.ex <= R < 2^(R.ex+1)
385
386    /* case R < 2^-1075: underflow case */
387    if result.exponent < -1075 {
388        return 0.5 * (s * f64::from_bits(0x0000000000000001));
389    }
390    if result.exponent >= 1025 {
391        return 1.0;
392    }
393
394    result.sign = if s == -1.0 {
395        DyadicSign::Neg
396    } else {
397        DyadicSign::Pos
398    };
399
400    result.fast_as_f64()
401}
402
403#[cold]
404#[inline(never)]
405fn mul_fixed_power_hard(x: f64, y: f64) -> f64 {
406    let mut s = TripleDouble::from_full_exact_add(1.0, x);
407    let mut iter_count = unsafe { y.abs().to_int_unchecked::<usize>() };
408
409    // exponentiation by squaring: O(log(y)) complexity
410    let mut acc = if iter_count % 2 != 0 {
411        s
412    } else {
413        TripleDouble::new(0., 0., 1.)
414    };
415
416    while {
417        iter_count >>= 1;
418        iter_count
419    } != 0
420    {
421        s = TripleDouble::quick_mult(s, s);
422        if iter_count % 2 != 0 {
423            acc = TripleDouble::quick_mult(acc, s);
424        }
425    }
426
427    if y.is_sign_negative() {
428        acc.recip().to_f64()
429    } else {
430        acc.to_f64()
431    }
432}
433
434#[cfg(test)]
435mod tests {
436    use super::*;
437    #[test]
438    fn test_compound() {
439        assert_eq!(f_compound(4831835136., -13.),0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012780345669344118 );
440        assert_eq!(
441            f_compound(11468322278342656., 2.9995136260713475),
442            1481455956234813000000000000000000000000000000000.
443        );
444        assert_eq!(f_compound(0.9999999999999999, 3.), 7.999999999999999);
445        assert_eq!(
446            f_compound(1.0039215087890625, 10.000000000349134),
447            1044.2562119607103
448        );
449        assert_eq!(f_compound(10., 18.0), 5559917313492231000.0);
450        assert_eq!(
451            f_compound(131071.65137729312, 2.000001423060894),
452            17180328027.532265
453        );
454        assert_eq!(f_compound(2., 5.), 243.);
455        assert_eq!(f_compound(126.4324324, 126.4324324), 1.4985383310514043e266);
456        assert_eq!(f_compound(0.4324324, 126.4324324), 5.40545942023447e19);
457        assert!(f_compound(-0.4324324, 126.4324324).is_nan());
458        assert_eq!(f_compound(0.0, 0.0), 1.0);
459        assert_eq!(f_compound(0.0, -1. / 2.), 1.0);
460        assert_eq!(f_compound(-1., -1. / 2.), f64::INFINITY);
461        assert_eq!(f_compound(f64::INFINITY, -1. / 2.), 0.0);
462        assert_eq!(f_compound(f64::INFINITY, 1. / 2.), f64::INFINITY);
463        assert_eq!(f_compound(46.3828125, 46.3828125), 5.248159634773675e77);
464    }
465
466    #[test]
467    fn test_compound_exotic_cases() {
468        assert_eq!(f_compound(0.9999999850987819, -1.), 0.5000000037253046);
469        assert_eq!(
470            f_compound(22427285907987670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.,
471                       -1.),
472            0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004458854290718438
473        );
474        assert_eq!(f_compound(0.786438105629145,  607.999512419221),
475                   1616461095392737200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.);
476        assert_eq!(f_compound( 1.0000002381857613, 960.8218657970428),
477                   17228671476562465000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.);
478        assert_eq!(f_compound(1., 1.0000000000000284), 2.);
479        assert_eq!(f_compound(1., f64::INFINITY), f64::INFINITY);
480        assert_eq!(
481            f_compound(10.000000000000007, -8.),
482            0.00000000466507380209731
483        );
484    }
485}