Skip to main content

decimal_scaled/
powers_fast.rs

1//! Lossy (f64-bridge) powers methods for D38.
2//!
3//! Companion to `powers_strict.rs`. The plain methods here are the
4//! f64-bridge variants, gated on std + (no strict feature or
5//! fast set). When strict is on, the dispatcher in the
6//! _strict file shadows these.
7
8use crate::core_type::D38;
9
10impl<const SCALE: u32> D38<SCALE> {
11
12    /// Raises `self` to the power `exp` via the f64 bridge.
13    ///
14    /// Converts both operands to f64, calls `f64::powf`, then converts
15    /// the result back. For integer exponents, prefer [`Self::pow`] or
16    /// [`Self::powi`], which are bit-exact.
17    ///
18    /// NaN results map to `ZERO`; infinities clamp to `MAX` or `MIN`,
19    /// following the saturate-vs-error policy of [`Self::from_f64`].
20    ///
21    /// # Precision
22    ///
23    /// Lossy: involves f64 at some point; result may lose precision.
24    ///
25    /// # Examples
26    ///
27    /// ```ignore
28    /// use decimal_scaled::D38s12;
29    /// let two = D38s12::from_int(2);
30    /// let three = D38s12::from_int(3);
31    /// // 2^3 = 8, within f64 precision.
32    /// assert!((two.powf(three).to_f64() - 8.0).abs() < 1e-9);
33    /// ```
34    #[cfg(feature = "std")]
35    #[inline]
36    #[must_use]
37    pub fn powf_fast(self, exp: D38<SCALE>) -> Self {
38        Self::from_f64(self.to_f64().powf(exp.to_f64()))
39    }
40
41    /// Returns the square root of `self` via the f64 bridge.
42    ///
43    /// IEEE 754 mandates that `f64::sqrt` is correctly-rounded
44    /// (round-to-nearest, ties-to-even). Combined with the deterministic
45    /// `to_f64` / `from_f64` round-trip, this makes
46    /// `D38::sqrt` bit-deterministic: the same input produces the same
47    /// output bit-pattern on every IEEE-754-conformant platform.
48    ///
49    /// Negative inputs produce a NaN from `f64::sqrt`, which
50    /// [`Self::from_f64`] maps to `ZERO` per the saturate-vs-error
51    /// policy. No panic is raised for negative inputs.
52    ///
53    /// # Precision
54    ///
55    /// Lossy: involves f64 at some point; result may lose precision.
56    ///
57    /// # Examples
58    ///
59    /// ```ignore
60    /// use decimal_scaled::D38s12;
61    /// assert_eq!(D38s12::ZERO.sqrt(), D38s12::ZERO);
62    /// // f64::sqrt(1.0) == 1.0 exactly, so the result is bit-exact.
63    /// assert_eq!(D38s12::ONE.sqrt(), D38s12::ONE);
64    /// ```
65    #[cfg(feature = "std")]
66    #[inline]
67    #[must_use]
68    pub fn sqrt_fast(self) -> Self {
69        Self::from_f64(self.to_f64().sqrt())
70    }
71
72    /// Returns the cube root of `self` via the f64 bridge.
73    ///
74    /// `f64::cbrt` is defined for the entire real line, including
75    /// negative inputs (`cbrt(-8.0) == -2.0`). The result is
76    /// bit-deterministic across IEEE-754-conformant platforms because
77    /// `f64::cbrt` is correctly-rounded.
78    ///
79    /// # Precision
80    ///
81    /// Lossy: involves f64 at some point; result may lose precision.
82    ///
83    /// # Examples
84    ///
85    /// ```ignore
86    /// use decimal_scaled::D38s12;
87    /// let neg_eight = D38s12::from_int(-8);
88    /// let result = neg_eight.cbrt();
89    /// assert!((result.to_f64() - (-2.0_f64)).abs() < 1e-9);
90    /// ```
91    #[cfg(feature = "std")]
92    #[inline]
93    #[must_use]
94    pub fn cbrt_fast(self) -> Self {
95        Self::from_f64(self.to_f64().cbrt())
96    }
97
98    // Integer power variant family.
99
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::from_int(3);
131    /// let four = D38s12::from_int(4);
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> D38<SCALE> {
159    /// Plain dispatcher: forwards to [`Self::powf_fast`] in this feature mode.
160    #[inline] #[must_use] pub fn powf(self, exp: D38<SCALE>) -> Self { self.powf_fast(exp) }
161    /// Plain dispatcher: forwards to [`Self::sqrt_fast`] in this feature mode.
162    #[inline] #[must_use] pub fn sqrt(self) -> Self { self.sqrt_fast() }
163    /// Plain dispatcher: forwards to [`Self::cbrt_fast`] in this feature mode.
164    #[inline] #[must_use] pub fn cbrt(self) -> Self { self.cbrt_fast() }
165    /// Plain dispatcher: forwards to [`Self::hypot_fast`] in this feature mode.
166    #[inline] #[must_use] pub fn hypot(self, other: Self) -> Self { self.hypot_fast(other) }
167}