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