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