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}