Skip to main content

decimal_scaled/
powers_strict.rs

1//! Power and root methods for [`D38`].
2//!
3//! # Methods
4//!
5//! - [`D38::pow`] — unsigned integer power via square-and-multiply over
6//! the `Mul` operator. Panics on overflow in debug builds; wraps in
7//! release builds. Matches `i128::pow` semantics.
8//! - [`D38::powi`] — signed integer power. For negative `exp`, returns
9//! `D38::ONE / self.pow(exp.unsigned_abs())`.
10//! - [`D38::powf`] — floating-point power via the f64 bridge. Lossy.
11//! Requires the `std` feature.
12//! - [`D38::sqrt`] — square root via the f64 bridge. IEEE 754 mandates
13//! that `f64::sqrt` is correctly-rounded, so identical inputs produce
14//! identical output bit-patterns on every conformant platform.
15//! Requires the `std` feature.
16//! - [`D38::cbrt`] — cube root via the f64 bridge. Defined for negative
17//! inputs. Requires the `std` feature.
18//! - [`D38::mul_add`] — `self * a + b` in one call. No hardware FMA;
19//! mirrors the `f64::mul_add` call shape so generic numeric code can
20//! monomorphise to `D38`. Always available.
21//! - [`D38::hypot`] — `sqrt(self^2 + other^2)` without intermediate
22//! overflow, using the scale-trick algorithm. Requires the `std`
23//! feature via `sqrt`.
24//!
25//! # The `*_strict` dual API
26//!
27//! `sqrt` / `cbrt` / `powf` / `hypot` each have an integer-only
28//! `*_strict` form and an f64-bridge form (see `docs/strict-mode.md`).
29//! The `*_strict` forms are **correctly rounded** — within 0.5 ULP of
30//! the exact result:
31//!
32//! - `sqrt_strict` / `cbrt_strict` form the exact 256-/384-bit
33//! radicand and take its exact integer root, rounding to nearest;
34//! - `powf_strict` runs `exp(y·ln(x))` entirely in the `d_w128_kernels`
35//! guard-digit intermediate;
36//! - `hypot_strict` composes `sqrt_strict` via the scale-trick.
37//!
38//! `pow` / `powi` (integer exponents) are exact at any feature
39//! configuration. The plain `sqrt` / `cbrt` / `powf` / `hypot`
40//! dispatch to the `*_strict` form under the `strict` feature, and to
41//! the f64 bridge otherwise; the `*_strict` forms are always compiled
42//! unless `fast` is set, and are `no_std`-compatible.
43//!
44//! # Variant family for `pow`
45//!
46//! - [`D38::checked_pow`] — `Option<Self>`, `None` on overflow at any
47//! step.
48//! - [`D38::wrapping_pow`] — two's-complement wrap at every step.
49//! - [`D38::saturating_pow`] — clamps to `D38::MAX` or `D38::MIN`
50//! based on the sign of the would-be result.
51//! - [`D38::overflowing_pow`] — `(Self, bool)`; the bool is `true` if
52//! any step overflowed, with the value equal to the wrapping form.
53//!
54//! # Square-and-multiply algorithm
55//!
56//! Starting from `acc = ONE`, the algorithm walks the bits of `exp` from
57//! low to high. On each iteration:
58//!
59//! 1. If the current bit of `exp` is set, multiply `acc *= base`.
60//! 2. Square `base *= base`.
61//!
62//! This costs `O(log exp)` multiplications rather than `O(exp)`. The
63//! variant family follows the same structure but applies the
64//! corresponding overflow arithmetic at every multiplication step.
65//!
66//! # `i32::MIN` edge case for `powi`
67//!
68//! `i32::unsigned_abs` returns `2_147_483_648_u32` for `i32::MIN`,
69//! avoiding the signed-negation overflow that `(-i32::MIN) as u32` would
70//! cause. `D38::ONE.powi(i32::MIN)` therefore evaluates correctly as
71//! `D38::ONE / D38::ONE.pow(2_147_483_648_u32)`.
72
73use crate::core_type::D38;
74use crate::mg_divide::mul_div_pow10;
75
76impl<const SCALE: u32> D38<SCALE> {
77    /// Raises `self` to the power `exp`.
78    ///
79    /// Uses square-and-multiply: walks the bits of `exp` from low to
80    /// high, squaring the base each step and accumulating when the
81    /// corresponding bit is set. Costs `O(log exp)` multiplications.
82    /// Each multiplication routes through the `D38` `Mul` operator.
83    ///
84    /// `exp = 0` always returns `ONE`, even when `self` is `ZERO`
85    /// (matches `i128::pow` convention).
86    ///
87    /// # Precision
88    ///
89    /// Strict: all arithmetic is integer-only; result is bit-exact.
90    ///
91    /// # Panics
92    ///
93    /// In debug builds, panics on `i128` overflow at any multiplication
94    /// step. In release builds, wraps two's-complement. Matches
95    /// `i128::pow` and `D38::Mul` semantics.
96    ///
97    /// Use [`Self::checked_pow`], [`Self::wrapping_pow`],
98    /// [`Self::saturating_pow`], or [`Self::overflowing_pow`] for
99    /// explicit overflow control.
100    ///
101    /// # Examples
102    ///
103    /// ```ignore
104    /// use decimal_scaled::D38s12;
105    /// let two = D38s12::from_int(2);
106    /// assert_eq!(two.pow(10), D38s12::from_int(1024));
107    /// // exp = 0 returns ONE regardless of base.
108    /// assert_eq!(D38s12::ZERO.pow(0), D38s12::ONE);
109    /// ```
110    #[inline]
111    #[must_use]
112    pub fn pow(self, exp: u32) -> Self {
113        let mut acc = Self::ONE;
114        let mut base = self;
115        let mut e = exp;
116        while e > 0 {
117            if e & 1 == 1 {
118                acc *= base;
119            }
120            e >>= 1;
121            if e > 0 {
122                base *= base;
123            }
124        }
125        acc
126    }
127
128    /// Raises `self` to the signed integer power `exp`.
129    ///
130    /// For non-negative `exp`, equivalent to `self.pow(exp as u32)`.
131    /// For negative `exp`, returns `D38::ONE / self.pow(exp.unsigned_abs())`,
132    /// i.e. the reciprocal of the positive-exponent form.
133    ///
134    /// # Precision
135    ///
136    /// Strict: all arithmetic is integer-only; result is bit-exact.
137    ///
138    /// # Panics
139    ///
140    /// - Overflow of `i128` storage at any step in debug builds (matches
141    /// [`Self::pow`]).
142    /// - Division by zero when `self == ZERO` and `exp < 0`.
143    ///
144    /// # Examples
145    ///
146    /// ```ignore
147    /// use decimal_scaled::D38s12;
148    /// let two = D38s12::from_int(2);
149    /// assert_eq!(two.powi(-1), D38s12::ONE / two);
150    /// assert_eq!(two.powi(0), D38s12::ONE);
151    /// assert_eq!(two.powi(3), D38s12::from_int(8));
152    /// ```
153    #[inline]
154    #[must_use]
155    pub fn powi(self, exp: i32) -> Self {
156        if exp >= 0 {
157            self.pow(exp as u32)
158        } else {
159            // unsigned_abs handles i32::MIN without signed-negation overflow.
160            Self::ONE / self.pow(exp.unsigned_abs())
161        }
162    }
163
164    /// Raises `self` to the power `exp` (strict integer-only stub).
165    ///
166    /// Converts both operands to f64, calls `f64::powf`, then converts
167    /// the result back. For integer exponents, prefer [`Self::pow`] or
168    /// [`Self::powi`], which are bit-exact.
169    ///
170    /// NaN results map to `ZERO`; infinities clamp to `MAX` or `MIN`,
171    /// following the saturate-vs-error policy of [`Self::from_f64`].
172    ///
173    /// # Precision
174    ///
175    /// Strict: all arithmetic is integer-only; result is bit-exact.
176    ///
177    /// # Examples
178    ///
179    /// ```ignore
180    /// use decimal_scaled::D38s12;
181    /// let two = D38s12::from_int(2);
182    /// let three = D38s12::from_int(3);
183    /// // 2^3 = 8, within f64 precision.
184    /// assert!((two.powf(three).to_f64() - 8.0).abs() < 1e-9);
185    /// ```
186    /// Raises `self` to the power `exp`, computed integer-only as
187    /// `exp(exp · ln(self))` — the `ln`, the `· exp`, and the `exp` all
188    /// run in the shared wide guard-digit intermediate, so the result
189    /// is correctly rounded (within 0.5 ULP).
190    ///
191    /// Always available, regardless of the `strict` feature. When
192    /// `strict` is enabled, the plain [`Self::powf`] delegates here.
193    ///
194    /// A zero or negative base saturates to `ZERO` (a negative base
195    /// with an arbitrary fractional exponent is not real-valued),
196    /// matching the f64-bridge NaN-to-ZERO policy.
197    #[inline]
198    #[must_use]
199    pub fn powf_strict(self, exp: D38<SCALE>) -> Self {
200        use crate::d_w128_kernels::Fixed;
201        if self.to_bits() <= 0 {
202            return Self::ZERO;
203        }
204        let guard = crate::log_exp_strict::STRICT_GUARD;
205        let w = SCALE + guard;
206        let pow = 10u128.pow(guard);
207        // ln(self) in the wide intermediate.
208        let ln_x = crate::log_exp_strict::ln_fixed(
209            Fixed::from_u128_mag(self.to_bits() as u128, false).mul_u128(pow),
210            w,
211        );
212        // y = exp, carried at the wide working scale (with its sign).
213        let y_neg = exp.to_bits() < 0;
214        let y_w = Fixed::from_u128_mag(exp.to_bits().unsigned_abs(), false).mul_u128(pow);
215        let y_w = if y_neg { y_w.neg() } else { y_w };
216        // exp(y · ln(x)), rounded once at the end.
217        let raw = crate::log_exp_strict::exp_fixed(y_w.mul(ln_x, w), w)
218            .round_to_i128(w, SCALE)
219            .expect("D38::powf: result overflows the representable range");
220        Self::from_bits(raw)
221    }
222
223    /// Raises `self` to the power `exp`.
224    ///
225    /// With the `strict` feature enabled this is the integer-only
226    /// [`Self::powf_strict`]; without it, the f64-bridge form.
227    #[cfg(all(feature = "strict", not(feature = "fast")))]
228    #[inline]
229    #[must_use]
230    pub fn powf(self, exp: D38<SCALE>) -> Self {
231        self.powf_strict(exp)
232    }
233
234    /// Returns the square root of `self` (strict integer-only stub).
235    ///
236    /// IEEE 754 mandates that `f64::sqrt` is correctly-rounded
237    /// (round-to-nearest, ties-to-even). Combined with the deterministic
238    /// `to_f64` / `from_f64` round-trip, this makes
239    /// `D38::sqrt` bit-deterministic: the same input produces the same
240    /// output bit-pattern on every IEEE-754-conformant platform.
241    ///
242    /// Negative inputs produce a NaN from `f64::sqrt`, which
243    /// [`Self::from_f64`] maps to `ZERO` per the saturate-vs-error
244    /// policy. No panic is raised for negative inputs.
245    ///
246    /// # Precision
247    ///
248    /// Strict: all arithmetic is integer-only; result is bit-exact.
249    ///
250    /// # Examples
251    ///
252    /// ```ignore
253    /// use decimal_scaled::D38s12;
254    /// assert_eq!(D38s12::ZERO.sqrt(), D38s12::ZERO);
255    /// // f64::sqrt(1.0) == 1.0 exactly, so the result is bit-exact.
256    /// assert_eq!(D38s12::ONE.sqrt(), D38s12::ONE);
257    /// ```
258    #[inline]
259    #[must_use]
260    pub fn sqrt_strict(self) -> Self {
261        // Correctly-rounded square root.
262        //
263        // For a `D38<SCALE>` with raw storage `r`, the logical value is
264        // `r / 10^SCALE`, and the raw storage of its square root is
265        //
266        // round( sqrt(r / 10^SCALE) · 10^SCALE )
267        // = round( sqrt(r · 10^SCALE) ).
268        //
269        // `r · 10^SCALE` is formed exactly as a 256-bit product and its
270        // integer square root is computed exactly, so the result is the
271        // exact square root correctly rounded to the type's last place
272        // (within 0.5 ULP — the IEEE-754 round-to-nearest result).
273        //
274        // Negative inputs saturate to ZERO, matching the f64-bridge
275        // policy (saturation, not panic).
276        if self.to_bits() <= 0 {
277            return Self::ZERO;
278        }
279        let raw = self.to_bits() as u128;
280        let q = crate::mg_divide::sqrt_raw_correctly_rounded(raw, SCALE);
281        Self::from_bits(q as i128)
282    }
283
284    /// Returns the square root of `self`.
285    ///
286    /// With the `strict` feature enabled this is the integer-only,
287    /// correctly-rounded [`Self::sqrt_strict`]; without it, the
288    /// f64-bridge form.
289    #[cfg(all(feature = "strict", not(feature = "fast")))]
290    #[inline]
291    #[must_use]
292    pub fn sqrt(self) -> Self {
293        self.sqrt_strict()
294    }
295
296    /// Returns the cube root of `self`.
297    ///
298    /// With the `strict` feature enabled this is the integer-only
299    /// [`Self::cbrt_strict`]; without it, the f64-bridge form.
300    #[cfg(all(feature = "strict", not(feature = "fast")))]
301    #[inline]
302    #[must_use]
303    pub fn cbrt(self) -> Self {
304        self.cbrt_strict()
305    }
306
307    /// Cube root of `self`. Defined for all reals — the sign of the
308    /// input is preserved (`cbrt(-8) = -2`).
309    ///
310    /// # Algorithm
311    ///
312    /// For a `D38<SCALE>` with raw storage `r`, the raw storage of the
313    /// cube root is
314    ///
315    /// round( cbrt(r / 10^SCALE) · 10^SCALE )
316    /// = round( cbrt(r · 10^(2·SCALE)) ).
317    ///
318    /// `r · 10^(2·SCALE)` is formed exactly as a 384-bit value and its
319    /// integer cube root is computed exactly, so the result is the
320    /// exact cube root correctly rounded to the type's last place
321    /// (within 0.5 ULP — the IEEE-754 round-to-nearest result).
322    ///
323    /// # Precision
324    ///
325    /// Strict: integer-only; correctly rounded.
326    #[inline]
327    #[must_use]
328    pub fn cbrt_strict(self) -> Self {
329        let raw = self.to_bits();
330        if raw == 0 {
331            return Self::ZERO;
332        }
333        let negative = raw < 0;
334        // `unsigned_abs` handles `i128::MIN` without the signed-negation
335        // overflow; the magnitude is at most 2^127.
336        let q = crate::mg_divide::cbrt_raw_correctly_rounded(raw.unsigned_abs(), SCALE);
337        // q < 2^127, so it fits i128 and its negation cannot overflow.
338        let result = q as i128;
339        Self::from_bits(if negative { -result } else { result })
340    }
341
342    /// Returns `sqrt(self^2 + other^2)` without intermediate overflow,
343    /// computed integer-only via the correctly-rounded
344    /// [`Self::sqrt_strict`]. Same scale-trick algorithm as the
345    /// f64-bridge [`Self::hypot`]; available in `no_std`.
346    ///
347    /// Always available, regardless of the `strict` feature.
348    #[inline]
349    #[must_use]
350    pub fn hypot_strict(self, other: Self) -> Self {
351        let a = self.abs();
352        let b = other.abs();
353        let (large, small) = if a >= b { (a, b) } else { (b, a) };
354        if large == Self::ZERO {
355            Self::ZERO
356        } else {
357            let ratio = small / large;
358            let one_plus_sq = Self::ONE + ratio * ratio;
359            large * one_plus_sq.sqrt_strict()
360        }
361    }
362
363    /// Returns `sqrt(self^2 + other^2)` without intermediate overflow.
364    ///
365    /// With the `strict` feature enabled this is the integer-only
366    /// [`Self::hypot_strict`]; without it, the f64-bridge form.
367    #[cfg(all(feature = "strict", not(feature = "fast")))]
368    #[inline]
369    #[must_use]
370    pub fn hypot(self, other: Self) -> Self {
371        self.hypot_strict(other)
372    }
373
374    // Overflow-variant family for pow.
375
376    /// Returns `Some(self^exp)`, or `None` if any multiplication step
377    /// overflows `i128`.
378    ///
379    /// Walks the same square-and-multiply as [`Self::pow`] but uses
380    /// `mul_div_pow10` (which returns `Option<i128>`) at each step.
381    /// The first `None` short-circuits to a `None` return.
382    ///
383    /// # Precision
384    ///
385    /// Strict: all arithmetic is integer-only; result is bit-exact.
386    ///
387    /// # Examples
388    ///
389    /// ```ignore
390    /// use decimal_scaled::D38s12;
391    /// // MAX^2 overflows.
392    /// assert!(D38s12::MAX.checked_pow(2).is_none());
393    /// // Any power of ONE is ONE.
394    /// assert_eq!(D38s12::ONE.checked_pow(1_000_000), Some(D38s12::ONE));
395    /// ```
396    #[inline]
397    #[must_use]
398    pub fn checked_pow(self, exp: u32) -> Option<Self> {
399        let mut acc: i128 = Self::ONE.0;
400        let mut base: i128 = self.0;
401        let mut e = exp;
402        while e > 0 {
403            if e & 1 == 1 {
404                acc = mul_div_pow10::<SCALE>(acc, base)?;
405            }
406            e >>= 1;
407            if e > 0 {
408                base = mul_div_pow10::<SCALE>(base, base)?;
409            }
410        }
411        Some(Self(acc))
412    }
413
414    /// Returns `self^exp`, wrapping two's-complement on overflow at
415    /// every multiplication step.
416    ///
417    /// Follows the same square-and-multiply structure as [`Self::pow`].
418    /// When a step overflows `mul_div_pow10`, the fallback is
419    /// `wrapping_mul` followed by `wrapping_div` of the scale
420    /// multiplier. The exact wrap pattern is deterministic and
421    /// reproducible but is not otherwise specified.
422    ///
423    /// # Precision
424    ///
425    /// Strict: all arithmetic is integer-only; result is bit-exact.
426    ///
427    /// # Examples
428    ///
429    /// ```ignore
430    /// use decimal_scaled::D38s12;
431    /// // ONE^N never overflows and returns ONE.
432    /// assert_eq!(D38s12::ONE.wrapping_pow(1_000_000), D38s12::ONE);
433    /// // MAX^2 wraps to a deterministic but unspecified value.
434    /// let _ = D38s12::MAX.wrapping_pow(2);
435    /// ```
436    #[inline]
437    #[must_use]
438    pub fn wrapping_pow(self, exp: u32) -> Self {
439        let mut acc: i128 = Self::ONE.0;
440        let mut base: i128 = self.0;
441        let mut e = exp;
442        let mult = Self::multiplier();
443        while e > 0 {
444            if e & 1 == 1 {
445                acc = match mul_div_pow10::<SCALE>(acc, base) {
446                    Some(q) => q,
447                    None => acc.wrapping_mul(base).wrapping_div(mult),
448                };
449            }
450            e >>= 1;
451            if e > 0 {
452                base = match mul_div_pow10::<SCALE>(base, base) {
453                    Some(q) => q,
454                    None => base.wrapping_mul(base).wrapping_div(mult),
455                };
456            }
457        }
458        Self(acc)
459    }
460
461    /// Returns `self^exp`, clamping to `D38::MAX` or `D38::MIN` on
462    /// overflow at any step.
463    ///
464    /// On the first step that overflows, the result is clamped based on
465    /// the sign of the mathematical result: positive overflows clamp to
466    /// `MAX`, negative overflows clamp to `MIN`. The sign of the result
467    /// is determined by `self.signum()` and whether `exp` is odd.
468    ///
469    /// `exp = 0` always returns `ONE` before entering the loop.
470    ///
471    /// # Precision
472    ///
473    /// Strict: all arithmetic is integer-only; result is bit-exact.
474    ///
475    /// # Examples
476    ///
477    /// ```ignore
478    /// use decimal_scaled::D38s12;
479    /// assert_eq!(D38s12::MAX.saturating_pow(2), D38s12::MAX);
480    /// assert_eq!(D38s12::ONE.saturating_pow(1_000_000), D38s12::ONE);
481    /// ```
482    #[inline]
483    #[must_use]
484    pub fn saturating_pow(self, exp: u32) -> Self {
485        // exp == 0: result is ONE by convention.
486        if exp == 0 {
487            return Self::ONE;
488        }
489        let mut acc: i128 = Self::ONE.0;
490        let mut base: i128 = self.0;
491        let mut e = exp;
492        // The final result is negative iff the base is negative and exp is odd.
493        let result_negative_if_overflow = self.0 < 0 && (exp & 1) == 1;
494        while e > 0 {
495            if e & 1 == 1 {
496                match mul_div_pow10::<SCALE>(acc, base) {
497                    Some(q) => acc = q,
498                    None => {
499                        return if result_negative_if_overflow {
500                            Self::MIN
501                        } else {
502                            Self::MAX
503                        };
504                    }
505                }
506            }
507            e >>= 1;
508            if e > 0 {
509                match mul_div_pow10::<SCALE>(base, base) {
510                    Some(q) => base = q,
511                    None => {
512                        // base*base is non-negative (squared); clamp by the
513                        // sign of the would-be final result.
514                        return if result_negative_if_overflow {
515                            Self::MIN
516                        } else {
517                            Self::MAX
518                        };
519                    }
520                }
521            }
522        }
523        Self(acc)
524    }
525
526    /// Returns `(self^exp, overflowed)`.
527    ///
528    /// `overflowed` is `true` if any multiplication step overflowed
529    /// `i128`. The returned value is the wrapping form (matches
530    /// [`Self::wrapping_pow`]).
531    ///
532    /// # Precision
533    ///
534    /// Strict: all arithmetic is integer-only; result is bit-exact.
535    ///
536    /// # Examples
537    ///
538    /// ```ignore
539    /// use decimal_scaled::D38s12;
540    /// let (_value, overflowed) = D38s12::MAX.overflowing_pow(2);
541    /// assert!(overflowed);
542    /// let (value, overflowed) = D38s12::ONE.overflowing_pow(5);
543    /// assert!(!overflowed);
544    /// assert_eq!(value, D38s12::ONE);
545    /// ```
546    #[inline]
547    #[must_use]
548    pub fn overflowing_pow(self, exp: u32) -> (Self, bool) {
549        let mut acc: i128 = Self::ONE.0;
550        let mut base: i128 = self.0;
551        let mut e = exp;
552        let mut overflowed = false;
553        let mult = Self::multiplier();
554        while e > 0 {
555            if e & 1 == 1 {
556                acc = if let Some(q) = mul_div_pow10::<SCALE>(acc, base) { q } else {
557                    overflowed = true;
558                    acc.wrapping_mul(base).wrapping_div(mult)
559                };
560            }
561            e >>= 1;
562            if e > 0 {
563                base = if let Some(q) = mul_div_pow10::<SCALE>(base, base) { q } else {
564                    overflowed = true;
565                    base.wrapping_mul(base).wrapping_div(mult)
566                };
567            }
568        }
569        (Self(acc), overflowed)
570    }
571}
572
573
574#[cfg(test)]
575mod tests {
576    use crate::core_type::D38s12;
577
578    // Tolerance for f64-bridge tests. Used only by std-feature-gated
579    // tests below; gated to suppress unused-item warnings on no_std builds.
580    #[cfg(feature = "std")]
581    const TWO_LSB: i128 = 2;
582
583    #[cfg(feature = "std")]
584    fn within_lsb(actual: D38s12, expected: D38s12, lsb: i128) -> bool {
585        let diff = (actual.to_bits() - expected.to_bits()).abs();
586        diff <= lsb
587    }
588
589    // pow (integer)
590
591    /// `pow(0)` returns ONE for a nonzero base.
592    #[test]
593    fn pow_zero_is_one_for_nonzero() {
594        let v = D38s12::from_int(7);
595        assert_eq!(v.pow(0), D38s12::ONE);
596    }
597
598    /// `pow(1)` returns self.
599    #[test]
600    fn pow_one_is_self() {
601        let v = D38s12::from_int(7);
602        assert_eq!(v.pow(1), v);
603    }
604
605    /// `pow(2)` equals `self * self` for an integer value.
606    #[test]
607    fn pow_two_matches_mul() {
608        let v = D38s12::from_int(13);
609        assert_eq!(v.pow(2), v * v);
610    }
611
612    /// `pow(2)` equals `self * self` for a fractional value.
613    #[test]
614    fn pow_two_matches_mul_fractional() {
615        // 1.5 in raw bits at SCALE = 12.
616        let v = D38s12::from_bits(1_500_000_000_000);
617        assert_eq!(v.pow(2), v * v);
618    }
619
620    /// `2^10 == 1024`.
621    #[test]
622    fn pow_two_to_the_ten() {
623        let two = D38s12::from_int(2);
624        assert_eq!(two.pow(10), D38s12::from_int(1024));
625    }
626
627    /// `pow(0, 0) == ONE` — matches `i128::pow(0, 0) == 1`.
628    #[test]
629    fn zero_pow_zero_is_one() {
630        assert_eq!(D38s12::ZERO.pow(0), D38s12::ONE);
631    }
632
633    /// `pow(0, n)` for `n > 0` is `ZERO`.
634    #[test]
635    fn zero_pow_positive_is_zero() {
636        assert_eq!(D38s12::ZERO.pow(1), D38s12::ZERO);
637        assert_eq!(D38s12::ZERO.pow(5), D38s12::ZERO);
638    }
639
640    /// `pow(n)` of `ONE` is always `ONE`.
641    #[test]
642    fn one_pow_n_is_one() {
643        assert_eq!(D38s12::ONE.pow(0), D38s12::ONE);
644        assert_eq!(D38s12::ONE.pow(1), D38s12::ONE);
645        assert_eq!(D38s12::ONE.pow(100), D38s12::ONE);
646    }
647
648    /// `(-1)^n` alternates sign.
649    #[test]
650    fn negative_one_pow_alternates() {
651        let neg_one = -D38s12::ONE;
652        assert_eq!(neg_one.pow(0), D38s12::ONE);
653        assert_eq!(neg_one.pow(1), neg_one);
654        assert_eq!(neg_one.pow(2), D38s12::ONE);
655        assert_eq!(neg_one.pow(3), neg_one);
656    }
657
658    // powi (signed integer)
659
660    /// `powi(0)` returns ONE.
661    #[test]
662    fn powi_zero_is_one() {
663        let v = D38s12::from_int(7);
664        assert_eq!(v.powi(0), D38s12::ONE);
665    }
666
667    /// `powi(1)` returns self.
668    #[test]
669    fn powi_one_is_self() {
670        let v = D38s12::from_int(7);
671        assert_eq!(v.powi(1), v);
672    }
673
674    /// `powi(-1)` returns `ONE / self`.
675    #[test]
676    fn powi_minus_one_is_reciprocal() {
677        let v = D38s12::from_int(7);
678        assert_eq!(v.powi(-1), D38s12::ONE / v);
679    }
680
681    /// `powi(-3)` equals `ONE / pow(3)`.
682    #[test]
683    fn powi_negative_matches_reciprocal_of_positive() {
684        let v = D38s12::from_int(2);
685        assert_eq!(v.powi(-3), D38s12::ONE / v.pow(3));
686    }
687
688    /// `powi` agrees with `pow` for non-negative exponents.
689    #[test]
690    fn powi_positive_matches_pow() {
691        let v = D38s12::from_int(3);
692        for e in 0_i32..6 {
693            assert_eq!(v.powi(e), v.pow(e as u32));
694        }
695    }
696
697    /// `i32::MIN` edge: `unsigned_abs` returns 2_147_483_648; for a base
698    /// of ONE the result is ONE.
699    #[test]
700    fn powi_i32_min_for_one_base() {
701        assert_eq!(D38s12::ONE.powi(i32::MIN), D38s12::ONE);
702    }
703
704    // powf — requires std feature
705
706    /// `powf(0.5)` approximates `sqrt` within 2 LSB.
707    #[cfg(feature = "std")]
708    #[test]
709    fn powf_half_matches_sqrt() {
710        let v = D38s12::from_int(4);
711        let half = D38s12::from_bits(500_000_000_000); // 0.5 at SCALE=12
712        let powf_result = v.powf(half);
713        let sqrt_result = v.sqrt();
714        assert!(
715            within_lsb(powf_result, sqrt_result, TWO_LSB),
716            "powf(0.5)={}, sqrt={}, diff={}",
717            powf_result.to_bits(),
718            sqrt_result.to_bits(),
719            (powf_result.to_bits() - sqrt_result.to_bits()).abs(),
720        );
721    }
722
723    /// `powf(2)` agrees with `pow(2)` within 2 LSB (f64 bridge).
724    #[cfg(all(feature = "std", any(not(feature = "strict"), feature = "fast")))]
725    #[test]
726    fn powf_two_matches_pow_two_within_lsb() {
727        let v = D38s12::from_int(7);
728        let two = D38s12::from_int(2);
729        assert!(within_lsb(v.powf(two), v.pow(2), TWO_LSB));
730    }
731
732    /// Strict `powf` is correctly rounded: `powf(7, 2)` agrees with the
733    /// exact `pow(7, 2)` to within 1 ULP — the whole `exp(y·ln(x))`
734    /// chain runs in the shared wide guard-digit intermediate.
735    #[cfg(all(feature = "strict", not(feature = "fast")))]
736    #[test]
737    fn powf_two_matches_pow_two_within_lsb() {
738        let v = D38s12::from_int(7);
739        let two = D38s12::from_int(2);
740        assert!(within_lsb(v.powf(two), v.pow(2), 1));
741        // A few more integer-exponent cross-checks against exact `pow`.
742        for base in [2_i64, 3, 5, 11] {
743            let b = D38s12::from_int(base);
744            assert!(
745                within_lsb(b.powf(D38s12::from_int(3)), b.pow(3), 1),
746                "powf({base}, 3)"
747            );
748        }
749    }
750
751    // sqrt — requires std feature
752
753    /// `sqrt(0) == 0`.
754    #[cfg(feature = "std")]
755    #[test]
756    fn sqrt_zero_is_zero() {
757        assert_eq!(D38s12::ZERO.sqrt(), D38s12::ZERO);
758    }
759
760    /// `sqrt(1) == 1` — bit-exact because `f64::sqrt(1.0) == 1.0`.
761    #[cfg(feature = "std")]
762    #[test]
763    fn sqrt_one_is_one_bit_exact() {
764        assert_eq!(D38s12::ONE.sqrt(), D38s12::ONE);
765    }
766
767    /// `sqrt(4) == 2` — bit-exact because `f64::sqrt(4.0) == 2.0`.
768    #[cfg(feature = "std")]
769    #[test]
770    fn sqrt_four_is_two() {
771        let four = D38s12::from_int(4);
772        let two = D38s12::from_int(2);
773        assert_eq!(four.sqrt(), two);
774    }
775
776    /// Strict `sqrt` is correctly rounded: for the raw result `q`, the
777    /// scaled radicand `N = r · 10^SCALE` must satisfy
778    /// `(q − 0.5)² ≤ N ≤ (q + 0.5)²`, i.e. `q` is the exact square root
779    /// rounded to nearest. Checked exactly in 256-bit integer space
780    /// across several scales and magnitudes.
781    #[test]
782    fn strict_sqrt_is_correctly_rounded() {
783        // (q - 0.5)^2 = q^2 - q + 0.25 → lower bound  N ≥ q^2 - q + 1 (ints, when q>0)
784        // (q + 0.5)^2 = q^2 + q + 0.25 → upper bound  N ≤ q^2 + q
785        // So a correctly-rounded q satisfies q^2 - q < N ≤ q^2 + q  (q>0),
786        // or N == 0 when q == 0.
787        fn check<const S: u32>(raw: i128) {
788            let x = crate::core_type::D38::<S>::from_bits(raw);
789            let q = x.sqrt_strict().to_bits();
790            assert!(q >= 0, "sqrt result must be non-negative");
791            // N = raw · 10^S as 256-bit; q is small enough that q^2 fits 256-bit.
792            let mult = 10u128.pow(S);
793            let (n_hi, n_lo) = crate::mg_divide::mul2(raw as u128, mult);
794            let (qsq_hi, qsq_lo) = crate::mg_divide::mul2(q as u128, q as u128);
795            // lower: N > q^2 - q ⇔   N + q > q^2   (q ≥ 0)
796            // upper: N ≤ q^2 + q
797            let q_u = q as u128;
798            // q^2 + q (256-bit)
799            let (uphi, uplo) = {
800                let (lo, c) = qsq_lo.overflowing_add(q_u);
801                (qsq_hi + c as u128, lo)
802            };
803            // N ≤ q^2 + q ?
804            let n_le_upper = n_hi < uphi || (n_hi == uphi && n_lo <= uplo);
805            assert!(n_le_upper, "sqrt({raw} @ s{S}) = {q}: N exceeds (q+0.5)^2");
806            if q > 0 {
807                // N + q (256-bit)
808                let (nphi, nplo) = {
809                    let (lo, c) = n_lo.overflowing_add(q_u);
810                    (n_hi + c as u128, lo)
811                };
812                // N + q > q^2 ?
813                let above_lower =
814                    nphi > qsq_hi || (nphi == qsq_hi && nplo > qsq_lo);
815                assert!(above_lower, "sqrt({raw} @ s{S}) = {q}: N below (q-0.5)^2");
816            }
817        }
818        for &raw in &[
819            1_i128,
820            2,
821            3,
822            4,
823            5,
824            999_999_999_999,
825            1_000_000_000_000,
826            1_500_000_000_000,
827            123_456_789_012_345,
828            i128::MAX,
829            i128::MAX / 7,
830        ] {
831            check::<0>(raw);
832            check::<6>(raw);
833            check::<12>(raw);
834            check::<19>(raw);
835        }
836        // High-scale cases where the radicand approaches the 256-bit cap.
837        for &raw in &[1_i128, 2, 17, i128::MAX, i128::MAX / 3] {
838            check::<38>(raw);
839        }
840    }
841
842    /// Strict `cbrt` is correctly rounded: for the raw result `q`, the
843    /// scaled radicand `N = |r| · 10^(2·SCALE)` must satisfy
844    /// `(2q − 1)³ < 8·N ≤ (2q + 1)³`, i.e. `q` is the exact cube root
845    /// rounded to nearest. Checked exactly in 384-bit integer space.
846    #[test]
847    fn strict_cbrt_is_correctly_rounded() {
848        // q correctly rounded ⇔  q − 0.5 < cbrt(N) ≤ q + 0.5
849        // ⇔  (2q − 1)³ < 8N ≤ (2q + 1)³.
850        // 384-bit comparison via num-bigint-free manual limbs would be
851        // verbose, so this check leans on the i256 dev-dependency to
852        // hold the 384-bit cubes (i256 is already a dev-dependency).
853        use i256::U256;
854        fn check<const S: u32>(raw: i128) {
855            let x = crate::core_type::D38::<S>::from_bits(raw);
856            let q = x.cbrt_strict().to_bits();
857            // Sign must match the input.
858            assert_eq!(q.signum(), raw.signum(), "cbrt sign mismatch");
859            let qa = q.unsigned_abs();
860            let ra = raw.unsigned_abs();
861            // N = |r| · 10^(2S). 2S ≤ 76, so 10^(2S) needs U256; the
862            // product needs more than 256 bits at high S, so cap the
863            // scales exercised here to keep the check in U256 range.
864            // (The 384-bit path itself is exercised across all scales by
865            // the round-trip tests; this exact check covers S ≤ 25.)
866            let m = U256::from(10u8).pow(2 * S);
867            let n = U256::from(ra) * m;
868            let eight_n = n << 3;
869            let two_q = U256::from(qa) * U256::from(2u8);
870            let upper = {
871                let t = two_q + U256::from(1u8);
872                t * t * t
873            };
874            assert!(eight_n <= upper, "cbrt({raw} @ s{S}) = {q}: 8N exceeds (2q+1)^3");
875            if qa > 0 {
876                let t = two_q - U256::from(1u8);
877                let lower = t * t * t;
878                assert!(eight_n > lower, "cbrt({raw} @ s{S}) = {q}: 8N at/below (2q-1)^3");
879            }
880        }
881        for &raw in &[
882            1_i128, 2, 7, 8, 9, 26, 27, 28,
883            999_999_999_999, 1_000_000_000_000, 123_456_789_012_345,
884            -8, -27, -1_000_000_000_000,
885        ] {
886            check::<0>(raw);
887            check::<6>(raw);
888            check::<12>(raw);
889        }
890        // Larger magnitudes at low scale (still within the U256 check).
891        for &raw in &[i128::MAX, i128::MIN + 1, i128::MAX / 11] {
892            check::<0>(raw);
893            check::<2>(raw);
894        }
895    }
896
897    /// `sqrt(self * self) ~= self.abs()` within 2 LSB.
898    #[cfg(feature = "std")]
899    #[test]
900    fn sqrt_of_square_recovers_abs() {
901        let v = D38s12::from_bits(1_234_567_890_123);
902        let squared = v * v;
903        let recovered = squared.sqrt();
904        let abs_v = v.abs();
905        assert!(
906            within_lsb(recovered, abs_v, TWO_LSB),
907            "sqrt({})={}, expected~={}, diff={}",
908            squared.to_bits(),
909            recovered.to_bits(),
910            abs_v.to_bits(),
911            (recovered.to_bits() - abs_v.to_bits()).abs(),
912        );
913    }
914
915    /// `sqrt(self * self) ~= self.abs()` within 2 LSB for negative self.
916    #[cfg(feature = "std")]
917    #[test]
918    fn sqrt_of_square_negative_recovers_abs() {
919        let v = -D38s12::from_bits(4_567_891_234_567);
920        let squared = v * v;
921        let recovered = squared.sqrt();
922        let abs_v = v.abs();
923        assert!(within_lsb(recovered, abs_v, TWO_LSB));
924    }
925
926    /// A negative input produces NaN in f64, which maps to ZERO.
927    #[cfg(feature = "std")]
928    #[test]
929    fn sqrt_negative_saturates_to_zero() {
930        let v = -D38s12::from_int(4);
931        assert_eq!(v.sqrt(), D38s12::ZERO);
932    }
933
934    // cbrt — requires std feature
935
936    /// `cbrt(0) == 0`.
937    #[cfg(feature = "std")]
938    #[test]
939    fn cbrt_zero_is_zero() {
940        assert_eq!(D38s12::ZERO.cbrt(), D38s12::ZERO);
941    }
942
943    /// `cbrt(1) == 1`.
944    #[cfg(feature = "std")]
945    #[test]
946    fn cbrt_one_is_one() {
947        assert_eq!(D38s12::ONE.cbrt(), D38s12::ONE);
948    }
949
950    /// `cbrt(8) ~= 2` within 2 LSB.
951    #[cfg(feature = "std")]
952    #[test]
953    fn cbrt_eight_is_two() {
954        let eight = D38s12::from_int(8);
955        let two = D38s12::from_int(2);
956        assert!(within_lsb(eight.cbrt(), two, TWO_LSB));
957    }
958
959    /// `cbrt(-8) ~= -2` within 2 LSB.
960    #[cfg(feature = "std")]
961    #[test]
962    fn cbrt_minus_eight_is_minus_two() {
963        let neg_eight = D38s12::from_int(-8);
964        let neg_two = D38s12::from_int(-2);
965        assert!(
966            within_lsb(neg_eight.cbrt(), neg_two, TWO_LSB),
967            "cbrt(-8) = {}, expected ~ {}",
968            neg_eight.cbrt().to_bits(),
969            neg_two.to_bits(),
970        );
971    }
972
973    // checked_pow / wrapping_pow / saturating_pow / overflowing_pow
974
975    /// `checked_pow(MAX, 2)` is `None` because MAX^2 overflows.
976    #[test]
977    fn checked_pow_max_squared_is_none() {
978        assert!(D38s12::MAX.checked_pow(2).is_none());
979    }
980
981    /// `checked_pow(ONE, N)` is `Some(ONE)` for any N.
982    #[test]
983    fn checked_pow_one_is_some_one() {
984        assert_eq!(D38s12::ONE.checked_pow(1_000_000), Some(D38s12::ONE));
985        assert_eq!(D38s12::ONE.checked_pow(0), Some(D38s12::ONE));
986    }
987
988    /// `checked_pow` agrees with `pow` for non-overflowing inputs.
989    #[test]
990    fn checked_pow_matches_pow_when_no_overflow() {
991        let v = D38s12::from_int(3);
992        assert_eq!(v.checked_pow(0), Some(v.pow(0)));
993        assert_eq!(v.checked_pow(1), Some(v.pow(1)));
994        assert_eq!(v.checked_pow(5), Some(v.pow(5)));
995    }
996
997    /// `saturating_pow(MAX, 2)` clamps to `MAX`.
998    #[test]
999    fn saturating_pow_max_squared_is_max() {
1000        assert_eq!(D38s12::MAX.saturating_pow(2), D38s12::MAX);
1001    }
1002
1003    /// `saturating_pow(MIN, 3)` clamps to `MIN` (negative result, odd exp).
1004    #[test]
1005    fn saturating_pow_min_cubed_is_min() {
1006        assert_eq!(D38s12::MIN.saturating_pow(3), D38s12::MIN);
1007    }
1008
1009    /// `saturating_pow(MIN, 2)` clamps to `MAX` (positive result, even exp).
1010    #[test]
1011    fn saturating_pow_min_squared_is_max() {
1012        assert_eq!(D38s12::MIN.saturating_pow(2), D38s12::MAX);
1013    }
1014
1015    /// `saturating_pow(ONE, N)` is ONE for any N.
1016    #[test]
1017    fn saturating_pow_one_is_one() {
1018        assert_eq!(D38s12::ONE.saturating_pow(1_000_000), D38s12::ONE);
1019    }
1020
1021    /// `saturating_pow(v, 0)` is ONE for any base.
1022    #[test]
1023    fn saturating_pow_zero_exp_is_one() {
1024        assert_eq!(D38s12::MAX.saturating_pow(0), D38s12::ONE);
1025        assert_eq!(D38s12::MIN.saturating_pow(0), D38s12::ONE);
1026    }
1027
1028    /// `overflowing_pow(MAX, 2)` returns `(wrapping_value, true)`.
1029    #[test]
1030    fn overflowing_pow_max_squared_flags_overflow() {
1031        let (value, overflowed) = D38s12::MAX.overflowing_pow(2);
1032        assert!(overflowed);
1033        assert_eq!(value, D38s12::MAX.wrapping_pow(2));
1034    }
1035
1036    /// `overflowing_pow(ONE, N)` never overflows.
1037    #[test]
1038    fn overflowing_pow_one_no_overflow() {
1039        let (value, overflowed) = D38s12::ONE.overflowing_pow(1_000_000);
1040        assert!(!overflowed);
1041        assert_eq!(value, D38s12::ONE);
1042    }
1043
1044    /// `overflowing_pow(v, 0)` is `(ONE, false)`.
1045    #[test]
1046    fn overflowing_pow_zero_exp_no_overflow() {
1047        let (value, overflowed) = D38s12::MAX.overflowing_pow(0);
1048        assert!(!overflowed);
1049        assert_eq!(value, D38s12::ONE);
1050    }
1051
1052    /// `wrapping_pow(MAX, 2)` matches the value half of `overflowing_pow`.
1053    #[test]
1054    fn wrapping_pow_max_squared_matches_overflowing() {
1055        let wrap = D38s12::MAX.wrapping_pow(2);
1056        let (over, _flag) = D38s12::MAX.overflowing_pow(2);
1057        assert_eq!(wrap, over);
1058    }
1059
1060    /// `wrapping_pow(ONE, N)` is ONE.
1061    #[test]
1062    fn wrapping_pow_one_is_one() {
1063        assert_eq!(D38s12::ONE.wrapping_pow(1_000_000), D38s12::ONE);
1064    }
1065
1066    /// `wrapping_pow` agrees with `pow` for non-overflowing inputs.
1067    #[test]
1068    fn wrapping_pow_matches_pow_when_no_overflow() {
1069        let v = D38s12::from_int(3);
1070        for e in 0..6 {
1071            assert_eq!(v.wrapping_pow(e), v.pow(e));
1072        }
1073    }
1074
1075    /// `pow(2) == self * self` for several representative raw values.
1076    #[test]
1077    fn pow_two_property_safe_values() {
1078        for raw in [
1079            1_234_567_890_123_i128,
1080            4_567_891_234_567_i128,
1081            7_890_123_456_789_i128,
1082        ] {
1083            let v = D38s12::from_bits(raw);
1084            assert_eq!(v.pow(2), v * v, "raw bits {raw}");
1085        }
1086    }
1087
1088    // mul_add (always available)
1089
1090    /// `mul_add(0, 0, 0) == 0`.
1091    #[test]
1092    fn mul_add_zero_zero_zero_is_zero() {
1093        let z = D38s12::ZERO;
1094        assert_eq!(z.mul_add(z, z), D38s12::ZERO);
1095    }
1096
1097    /// `mul_add(2, 3, 4) == 10`.
1098    #[test]
1099    fn mul_add_two_three_four_is_ten() {
1100        let two = D38s12::from_int(2);
1101        let three = D38s12::from_int(3);
1102        let four = D38s12::from_int(4);
1103        assert_eq!(two.mul_add(three, four), D38s12::from_int(10));
1104    }
1105
1106    /// `mul_add(self, ONE, ZERO) == self`.
1107    #[test]
1108    fn mul_add_identity_collapses() {
1109        let v = D38s12::from_int(7);
1110        assert_eq!(v.mul_add(D38s12::ONE, D38s12::ZERO), v);
1111    }
1112
1113    /// `mul_add(self, ZERO, b) == b`.
1114    #[test]
1115    fn mul_add_zero_factor_yields_addend() {
1116        let v = D38s12::from_int(7);
1117        let b = D38s12::from_int(13);
1118        assert_eq!(v.mul_add(D38s12::ZERO, b), b);
1119    }
1120
1121    /// `mul_add(a, b, c) == a * b + c` for representative raw values.
1122    #[test]
1123    fn mul_add_matches_mul_then_add_safe_values() {
1124        for (a_raw, b_raw, c_raw) in [
1125            (1_234_567_890_123_i128, 2_345_678_901_234_i128, 3_456_789_012_345_i128),
1126            (4_567_891_234_567_i128, 5_678_912_345_678_i128, 6_789_123_456_789_i128),
1127            (7_890_123_456_789_i128, 8_901_234_567_891_i128, 9_012_345_678_912_i128),
1128        ] {
1129            let a = D38s12::from_bits(a_raw);
1130            let b = D38s12::from_bits(b_raw);
1131            let c = D38s12::from_bits(c_raw);
1132            assert_eq!(
1133                a.mul_add(b, c),
1134                a * b + c,
1135                "raw bits ({a_raw}, {b_raw}, {c_raw})",
1136            );
1137        }
1138    }
1139
1140    /// `(-a).mul_add(b, c)` propagates the sign through the multiply step.
1141    #[test]
1142    fn mul_add_sign_propagates_through_factor() {
1143        let a = D38s12::from_int(3);
1144        let b = D38s12::from_int(5);
1145        let c = D38s12::from_int(7);
1146        // (-3) * 5 + 7 = -15 + 7 = -8
1147        assert_eq!((-a).mul_add(b, c), D38s12::from_int(-8));
1148    }
1149
1150    // hypot — requires std feature
1151
1152    // Tolerance for hypot: composes divide + square + add + sqrt + multiply,
1153    // each with up to 1 LSB of f64-bridge slack; sqrt quantisation dominates.
1154    #[cfg(feature = "std")]
1155    const HYPOT_TOLERANCE_LSB: i128 = 32;
1156
1157    /// `hypot(3, 4) ~= 5` — the Pythagorean triple.
1158    #[cfg(feature = "std")]
1159    #[test]
1160    fn hypot_three_four_is_five() {
1161        let three = D38s12::from_int(3);
1162        let four = D38s12::from_int(4);
1163        let five = D38s12::from_int(5);
1164        let result = three.hypot(four);
1165        assert!(
1166            within_lsb(result, five, HYPOT_TOLERANCE_LSB),
1167            "hypot(3, 4)={}, expected~={}, diff={}",
1168            result.to_bits(),
1169            five.to_bits(),
1170            (result.to_bits() - five.to_bits()).abs(),
1171        );
1172    }
1173
1174    /// `hypot(0, 0) == 0` — bit-exact via the early-return path.
1175    #[cfg(feature = "std")]
1176    #[test]
1177    fn hypot_zero_zero_is_zero_bit_exact() {
1178        assert_eq!(D38s12::ZERO.hypot(D38s12::ZERO), D38s12::ZERO);
1179    }
1180
1181    /// `hypot(0, x) ~= x.abs()` for nonzero x.
1182    #[cfg(feature = "std")]
1183    #[test]
1184    fn hypot_zero_x_is_abs_x() {
1185        let x = D38s12::from_int(7);
1186        let result = D38s12::ZERO.hypot(x);
1187        assert!(
1188            within_lsb(result, x.abs(), HYPOT_TOLERANCE_LSB),
1189            "hypot(0, 7)={}, expected~={}",
1190            result.to_bits(),
1191            x.abs().to_bits(),
1192        );
1193    }
1194
1195    /// `hypot(x, 0) ~= x.abs()` for nonzero x.
1196    #[cfg(feature = "std")]
1197    #[test]
1198    fn hypot_x_zero_is_abs_x() {
1199        let x = D38s12::from_int(-9);
1200        let result = x.hypot(D38s12::ZERO);
1201        assert!(
1202            within_lsb(result, x.abs(), HYPOT_TOLERANCE_LSB),
1203            "hypot(-9, 0)={}, expected~={}",
1204            result.to_bits(),
1205            x.abs().to_bits(),
1206        );
1207    }
1208
1209    /// `hypot(-a, b) == hypot(a, b)` — sign invariance from the abs step.
1210    #[cfg(feature = "std")]
1211    #[test]
1212    fn hypot_sign_invariant() {
1213        let three = D38s12::from_int(3);
1214        let four = D38s12::from_int(4);
1215        let pos = three.hypot(four);
1216        let neg_a = (-three).hypot(four);
1217        let neg_b = three.hypot(-four);
1218        let neg_both = (-three).hypot(-four);
1219        assert_eq!(pos, neg_a);
1220        assert_eq!(pos, neg_b);
1221        assert_eq!(pos, neg_both);
1222    }
1223
1224    /// `hypot` does not panic at large magnitudes that the naive form
1225    /// would overflow.
1226    ///
1227    /// At SCALE=12 with `i128::MAX / 2` raw bits, the true hypotenuse
1228    /// is well below `D38::MAX / sqrt(2)`, so no overflow occurs and
1229    /// the result is a nonzero positive value.
1230    #[cfg(feature = "std")]
1231    #[test]
1232    fn hypot_large_magnitudes_does_not_panic() {
1233        let half_max = D38s12::from_bits(i128::MAX / 2);
1234        let result = half_max.hypot(half_max);
1235        assert!(result > D38s12::ZERO);
1236        assert!(result >= half_max);
1237    }
1238
1239    /// `hypot(a, b)` matches the naive `sqrt(a^2 + b^2)` within tolerance
1240    /// for small magnitudes where the naive form does not overflow.
1241    #[cfg(feature = "std")]
1242    #[test]
1243    fn hypot_matches_naive_sqrt_of_sum_of_squares() {
1244        let a = D38s12::from_int(12);
1245        let b = D38s12::from_int(13);
1246        let h = a.hypot(b);
1247        let naive = (a * a + b * b).sqrt();
1248        assert!(
1249            within_lsb(h, naive, HYPOT_TOLERANCE_LSB),
1250            "hypot(12, 13)={}, naive sqrt(a^2+b^2)={}, diff={}",
1251            h.to_bits(),
1252            naive.to_bits(),
1253            (h.to_bits() - naive.to_bits()).abs(),
1254        );
1255    }
1256}
1257