Skip to main content

decimal_scaled/types/
powers_fast.rs

1// SPDX-FileCopyrightText: 2026 John Moxley
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Lossy (f64-bridge) powers methods for D38.
5//!
6//! Companion to `types/powers.rs`. The plain methods here are the
7//! f64-bridge variants, gated on std + (no strict feature or
8//! fast set). When strict is on, the dispatcher in the
9//! _strict file shadows these.
10
11
12impl<const SCALE: u32> crate::D<crate::int::types::Int<2>, SCALE> {
13    /// Raises `self` to the power `exp` via the f64 bridge.
14    ///
15    /// Converts both operands to f64, calls `f64::powf`, then converts
16    /// the result back. For integer exponents, prefer [`Self::pow`] or
17    /// [`Self::powi`], which are bit-exact.
18    ///
19    /// NaN results map to `ZERO`; infinities clamp to `MAX` or `MIN`,
20    /// following the saturate-vs-error policy of [`Self::from_f64`].
21    ///
22    /// # Precision
23    ///
24    /// Lossy: involves f64 at some point; result may lose precision.
25    ///
26    /// # Examples
27    ///
28    /// ```ignore
29    /// use decimal_scaled::D38s12;
30    /// let two = D38s12::try_from(2).unwrap();
31    /// let three = D38s12::try_from(3).unwrap();
32    /// // 2^3 = 8, within f64 precision.
33    /// assert!((two.powf(three).to_f64() - 8.0).abs() < 1e-9);
34    /// ```
35    #[cfg(feature = "std")]
36    #[inline]
37    #[must_use]
38    pub fn powf_fast(self, exp: crate::D<crate::int::types::Int<2>, SCALE>) -> Self {
39        Self::from_f64(self.to_f64().powf(exp.to_f64()))
40    }
41
42    /// Returns the square root of `self` via the f64 bridge.
43    ///
44    /// IEEE 754 mandates that `f64::sqrt` is correctly-rounded
45    /// (round-to-nearest, ties-to-even). Combined with the deterministic
46    /// `to_f64` / `from_f64` round-trip, this makes
47    /// `D38::sqrt` bit-deterministic: the same input produces the same
48    /// output bit-pattern on every IEEE-754-conformant platform.
49    ///
50    /// Negative inputs produce a NaN from `f64::sqrt`, which
51    /// [`Self::from_f64`] maps to `ZERO` per the saturate-vs-error
52    /// policy. No panic is raised for negative inputs.
53    ///
54    /// # Precision
55    ///
56    /// Lossy: involves f64 at some point; result may lose precision.
57    ///
58    /// # Examples
59    ///
60    /// ```ignore
61    /// use decimal_scaled::D38s12;
62    /// assert_eq!(D38s12::ZERO.sqrt(), D38s12::ZERO);
63    /// // f64::sqrt(1.0) == 1.0 exactly, so the result is bit-exact.
64    /// assert_eq!(D38s12::ONE.sqrt(), D38s12::ONE);
65    /// ```
66    #[cfg(feature = "std")]
67    #[inline]
68    #[must_use]
69    pub fn sqrt_fast(self) -> Self {
70        Self::from_f64(self.to_f64().sqrt())
71    }
72
73    /// Returns the cube root of `self` via the f64 bridge.
74    ///
75    /// `f64::cbrt` is defined for the entire real line, including
76    /// negative inputs (`cbrt(-8.0) == -2.0`). The result is
77    /// bit-deterministic across IEEE-754-conformant platforms because
78    /// `f64::cbrt` is correctly-rounded.
79    ///
80    /// # Precision
81    ///
82    /// Lossy: involves f64 at some point; result may lose precision.
83    ///
84    /// # Examples
85    ///
86    /// ```ignore
87    /// use decimal_scaled::D38s12;
88    /// let neg_eight = D38s12::try_from(-8).unwrap();
89    /// let result = neg_eight.cbrt();
90    /// assert!((result.to_f64() - (-2.0_f64)).abs() < 1e-9);
91    /// ```
92    #[cfg(feature = "std")]
93    #[inline]
94    #[must_use]
95    pub fn cbrt_fast(self) -> Self {
96        Self::from_f64(self.to_f64().cbrt())
97    }
98
99    // Integer power variant family.
100
101    /// Returns `sqrt(self^2 + other^2)` without intermediate overflow.
102    ///
103    /// The naive form `(self * self + other * other).sqrt()` overflows
104    /// `i128` once either operand approaches `sqrt(D38::MAX)`. This
105    /// method uses the scale trick to avoid that:
106    ///
107    /// ```text
108    /// hypot(a, b) = max(|a|, |b|) * sqrt(1 + (min(|a|, |b|) / max(|a|, |b|))^2)
109    /// ```
110    ///
111    /// The `min/max` ratio is in `[0, 1]`, so `ratio^2` is also in
112    /// `[0, 1]` and cannot overflow. The outer multiply by `large` only
113    /// overflows when the true hypotenuse genuinely exceeds `D38::MAX`,
114    /// which matches `f64::hypot`'s contract.
115    ///
116    /// Both inputs are absolute-valued before processing, so
117    /// `hypot(-a, b) == hypot(a, b)`.
118    ///
119    /// Edge cases: `hypot(0, 0) == 0` (bit-exact via the early return);
120    /// `hypot(0, x) ~= |x|` and `hypot(x, 0) ~= |x|`.
121    ///
122    /// # Precision
123    ///
124    /// Lossy: involves f64 at some point; result may lose precision.
125    ///
126    /// # Examples
127    ///
128    /// ```ignore
129    /// use decimal_scaled::D38s12;
130    /// let three = D38s12::try_from(3).unwrap();
131    /// let four = D38s12::try_from(4).unwrap();
132    /// // Pythagorean triple: hypot(3, 4) ~= 5.
133    /// assert!((three.hypot(four).to_f64() - 5.0).abs() < 1e-9);
134    /// ```
135    #[cfg(feature = "std")]
136    #[inline]
137    #[must_use]
138    pub fn hypot_fast(self, other: Self) -> Self {
139        let a = self.abs();
140        let b = other.abs();
141        let (large, small) = if a >= b { (a, b) } else { (b, a) };
142        if large == Self::ZERO {
143            // Both inputs are zero; large is the max of two non-negatives,
144            // so this branch is only reached when both are zero.
145            Self::ZERO
146        } else {
147            let ratio = small / large;
148            // ratio^2 is in [0, 1]; ONE + ratio^2 is in [1, 2]; no overflow.
149            // The outer sqrt is in [1, sqrt(2)]; the final multiply by large
150            // only overflows when the true hypotenuse exceeds D38::MAX.
151            let one_plus_sq = Self::ONE + ratio * ratio;
152            large * one_plus_sq.sqrt_fast()
153        }
154    }
155}
156
157#[cfg(all(feature = "std", any(not(feature = "strict"), feature = "fast")))]
158impl<const SCALE: u32> crate::D<crate::int::types::Int<2>, SCALE> {
159    /// Plain dispatcher: forwards to [`Self::powf_fast`] in this feature mode.
160    #[inline]
161    #[must_use]
162    pub fn powf(self, exp: crate::D<crate::int::types::Int<2>, SCALE>) -> Self {
163        self.powf_fast(exp)
164    }
165    /// Plain dispatcher: forwards to [`Self::sqrt_fast`] in this feature mode.
166    #[inline]
167    #[must_use]
168    pub fn sqrt(self) -> Self {
169        self.sqrt_fast()
170    }
171    /// Plain dispatcher: forwards to [`Self::cbrt_fast`] in this feature mode.
172    #[inline]
173    #[must_use]
174    pub fn cbrt(self) -> Self {
175        self.cbrt_fast()
176    }
177    /// Plain dispatcher: forwards to [`Self::hypot_fast`] in this feature mode.
178    #[inline]
179    #[must_use]
180    pub fn hypot(self, other: Self) -> Self {
181        self.hypot_fast(other)
182    }
183}