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}