Skip to main content

decimal_scaled/types/
powers.rs

1// SPDX-FileCopyrightText: 2026 John Moxley
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Power and root methods for [`D38`].
5//!
6//! # Methods
7//!
8//! - [`D38::pow`] — unsigned integer power via square-and-multiply over
9//! the `Mul` operator. Panics on overflow in debug builds; wraps in
10//! release builds. Matches `i128::pow` semantics.
11//! - [`D38::powi`] — signed integer power. For negative `exp`, returns
12//! `D38::ONE / self.pow(exp.unsigned_abs())`.
13//! - [`D38::powf`] — floating-point power via the f64 bridge. Lossy.
14//! Requires the `std` feature.
15//! - [`D38::sqrt`] — square root via the f64 bridge. IEEE 754 mandates
16//! that `f64::sqrt` is correctly-rounded, so identical inputs produce
17//! identical output bit-patterns on every conformant platform.
18//! Requires the `std` feature.
19//! - [`D38::cbrt`] — cube root via the f64 bridge. Defined for negative
20//! inputs. Requires the `std` feature.
21//! - [`D38::mul_add`] — `self * a + b` in one call. No hardware FMA;
22//! mirrors the `f64::mul_add` call shape so generic numeric code can
23//! monomorphise to `D38`. Always available.
24//! - [`D38::hypot`] — `sqrt(self^2 + other^2)` without intermediate
25//! overflow, using the scale-trick algorithm. Requires the `std`
26//! feature via `sqrt`.
27//!
28//! # The `*_strict` dual API
29//!
30//! `sqrt` / `cbrt` / `powf` / `hypot` each have an integer-only
31//! `*_strict` form and an f64-bridge form (see `docs/strict-mode.md`).
32//! The `*_strict` forms are **correctly rounded** — within 0.5 ULP of
33//! the exact result under the active [`RoundingMode`]:
34//!
35//! - `sqrt_strict` / `cbrt_strict` form the exact 256-/384-bit
36//! radicand and take its exact integer root, then apply the rounding
37//! mode (no ties exist for integer-sqrt so the three half-modes
38//! coincide; `Floor`/`Ceiling` divert for the directed cases);
39//! - `powf_strict` runs `exp(y·ln(x))` entirely in the `algos::support::fixed`
40//! guard-digit intermediate;
41//! - `hypot_strict` composes `sqrt_strict` via the scale-trick.
42//!
43//! Each strict method has a `*_strict_with(mode)` sibling that takes
44//! the rounding mode explicitly; the no-arg `*_strict` form
45//! delegates to it with the crate-default mode (see
46//! [`crate::RoundingMode`] for the `rounding-*` Cargo features).
47//! `powf` additionally ships `powf_approx(working_digits)` and
48//! `powf_approx_with(working_digits, mode)` — the four-variant matrix
49//! the transcendentals expose; `sqrt` / `cbrt` / `hypot` have no
50//! guard-width parameter (the exact-integer-root path is precision-
51//! independent), so only the `_strict` / `_strict_with` pair exists.
52//!
53//! `pow` / `powi` (integer exponents) are exact at any feature
54//! configuration. The plain `sqrt` / `cbrt` / `powf` / `hypot`
55//! dispatch to the `*_strict` form under the `strict` feature, and to
56//! the f64 bridge otherwise; the `*_strict` forms are always compiled
57//! unless `fast` is set, and are `no_std`-compatible.
58//!
59//! [`RoundingMode`]: crate::RoundingMode
60//!
61//! # Variant family for `pow`
62//!
63//! - [`D38::checked_pow`] — `Option<Self>`, `None` on overflow at any
64//! step.
65//! - [`D38::wrapping_pow`] — two's-complement wrap at every step.
66//! - [`D38::saturating_pow`] — clamps to `D38::MAX` or `D38::MIN`
67//! based on the sign of the would-be result.
68//! - [`D38::overflowing_pow`] — `(Self, bool)`; the bool is `true` if
69//! any step overflowed, with the value equal to the wrapping form.
70//!
71//! # Square-and-multiply algorithm
72//!
73//! Starting from `acc = ONE`, the algorithm walks the bits of `exp` from
74//! low to high. On each iteration:
75//!
76//! 1. If the current bit of `exp` is set, multiply `acc *= base`.
77//! 2. Square `base *= base`.
78//!
79//! This costs `O(log exp)` multiplications rather than `O(exp)`. The
80//! variant family follows the same structure but applies the
81//! corresponding overflow arithmetic at every multiplication step.
82//!
83//! # `i32::MIN` edge case for `powi`
84//!
85//! `i32::unsigned_abs` returns `2_147_483_648_u32` for `i32::MIN`,
86//! avoiding the signed-negation overflow that `(-i32::MIN) as u32` would
87//! cause. `D38::ONE.powi(i32::MIN)` therefore evaluates correctly as
88//! `D38::ONE / D38::ONE.pow(2_147_483_648_u32)`.
89
90
91impl<const SCALE: u32> crate::D<crate::int::types::Int<2>, SCALE> {
92    /// Raises `self` to the power `exp`.
93    ///
94    /// Uses square-and-multiply: walks the bits of `exp` from low to
95    /// high, squaring the base each step and accumulating when the
96    /// corresponding bit is set. Costs `O(log exp)` multiplications.
97    /// Each multiplication routes through the `D38` `Mul` operator.
98    ///
99    /// `exp = 0` always returns `ONE`, even when `self` is `ZERO`
100    /// (matches `i128::pow` convention).
101    ///
102    /// # Precision
103    ///
104    /// Strict: all arithmetic is integer-only; result is bit-exact.
105    ///
106    /// # Panics
107    ///
108    /// In debug builds, panics on `i128` overflow at any multiplication
109    /// step. In release builds, wraps two's-complement. Matches
110    /// `i128::pow` and `D38::Mul` semantics.
111    ///
112    /// Use [`Self::checked_pow`], [`Self::wrapping_pow`],
113    /// [`Self::saturating_pow`], or [`Self::overflowing_pow`] for
114    /// explicit overflow control.
115    ///
116    /// # Examples
117    ///
118    /// ```ignore
119    /// use decimal_scaled::D38s12;
120    /// let two = D38s12::try_from(2).unwrap();
121    /// assert_eq!(two.pow(10), D38s12::try_from(1024).unwrap());
122    /// // exp = 0 returns ONE regardless of base.
123    /// assert_eq!(D38s12::ZERO.pow(0), D38s12::ONE);
124    /// ```
125    #[inline]
126    #[must_use]
127    pub fn pow(self, exp: u32) -> Self {
128        let mut acc = Self::ONE;
129        let mut base = self;
130        let mut e = exp;
131        while e > 0 {
132            if e & 1 == 1 {
133                acc *= base;
134            }
135            e >>= 1;
136            if e > 0 {
137                base *= base;
138            }
139        }
140        acc
141    }
142
143    /// Raises `self` to the signed integer power `exp`.
144    ///
145    /// For non-negative `exp`, equivalent to `self.pow(exp as u32)`.
146    /// For negative `exp`, returns `D38::ONE / self.pow(exp.unsigned_abs())`,
147    /// i.e. the reciprocal of the positive-exponent form.
148    ///
149    /// # Precision
150    ///
151    /// Strict: all arithmetic is integer-only; result is bit-exact.
152    ///
153    /// # Panics
154    ///
155    /// - Overflow of `i128` storage at any step in debug builds (matches
156    /// [`Self::pow`]).
157    /// - Division by zero when `self == ZERO` and `exp < 0`.
158    ///
159    /// # Examples
160    ///
161    /// ```ignore
162    /// use decimal_scaled::D38s12;
163    /// let two = D38s12::try_from(2).unwrap();
164    /// assert_eq!(two.powi(-1), D38s12::ONE / two);
165    /// assert_eq!(two.powi(0), D38s12::ONE);
166    /// assert_eq!(two.powi(3), D38s12::try_from(8).unwrap());
167    /// ```
168    #[inline]
169    #[must_use]
170    pub fn powi(self, exp: i32) -> Self {
171        if exp >= 0 {
172            self.pow(exp as u32)
173        } else {
174            // unsigned_abs handles i32::MIN without signed-negation overflow.
175            Self::ONE / self.pow(exp.unsigned_abs())
176        }
177    }
178
179    /// Raises `self` to the power `exp` (strict integer-only stub).
180    ///
181    /// Converts both operands to f64, calls `f64::powf`, then converts
182    /// the result back. For integer exponents, prefer [`Self::pow`] or
183    /// [`Self::powi`], which are bit-exact.
184    ///
185    /// NaN results map to `ZERO`; infinities clamp to `MAX` or `MIN`,
186    /// following the saturate-vs-error policy of [`Self::from_f64`].
187    ///
188    /// # Precision
189    ///
190    /// Strict: all arithmetic is integer-only; result is bit-exact.
191    ///
192    /// # Examples
193    ///
194    /// ```ignore
195    /// use decimal_scaled::D38s12;
196    /// let two = D38s12::try_from(2).unwrap();
197    /// let three = D38s12::try_from(3).unwrap();
198    /// // 2^3 = 8, within f64 precision.
199    /// assert!((two.powf(three).to_f64() - 8.0).abs() < 1e-9);
200    /// ```
201    /// Raises `self` to the power `exp`, computed integer-only as
202    /// `exp(exp · ln(self))` — the `ln`, the `· exp`, and the `exp` all
203    /// run in the shared wide guard-digit intermediate, so the result
204    /// is correctly rounded (within 0.5 ULP).
205    ///
206    /// Always available, regardless of the `strict` feature. When
207    /// `strict` is enabled, the plain [`Self::powf`] delegates here.
208    ///
209    /// A zero or negative base saturates to `ZERO` (a negative base
210    /// with an arbitrary fractional exponent is not real-valued),
211    /// matching the f64-bridge NaN-to-ZERO policy.
212    #[inline]
213    #[must_use]
214    pub fn powf_strict(self, exp: crate::D<crate::int::types::Int<2>, SCALE>) -> Self {
215        self.powf_strict_with(exp, crate::support::rounding::DEFAULT_ROUNDING_MODE)
216    }
217
218    /// `self^exp` under the supplied rounding mode.
219    #[inline]
220    #[must_use]
221    pub fn powf_strict_with(
222        self,
223        exp: crate::D<crate::int::types::Int<2>, SCALE>,
224        mode: crate::support::rounding::RoundingMode,
225    ) -> Self {
226        Self::from_bits(crate::policy::pow::dispatch::<_, SCALE>(self.to_bits(), exp.to_bits(), mode))
227    }
228
229    /// `self^exp` with caller-chosen guard digits.
230    #[inline]
231    #[must_use]
232    pub fn powf_approx(self, exp: crate::D<crate::int::types::Int<2>, SCALE>, working_digits: u32) -> Self {
233        self.powf_approx_with(
234            exp,
235            working_digits,
236            crate::support::rounding::DEFAULT_ROUNDING_MODE,
237        )
238    }
239
240    /// `self^exp` with caller-chosen guard digits AND rounding mode.
241    #[inline]
242    #[must_use]
243    pub fn powf_approx_with(
244        self,
245        exp: crate::D<crate::int::types::Int<2>, SCALE>,
246        working_digits: u32,
247        mode: crate::support::rounding::RoundingMode,
248    ) -> Self {
249        if working_digits == crate::types::log_exp::STRICT_GUARD {
250            return self.powf_strict_with(exp, mode);
251        }
252        Self::from_bits(crate::policy::pow::dispatch_with::<_, SCALE>(self.to_bits(), exp.to_bits(), working_digits, mode))
253    }
254
255    /// Raises `self` to the power `exp`.
256    ///
257    /// With the `strict` feature enabled this is the integer-only
258    /// [`Self::powf_strict`]; without it, the f64-bridge form.
259    #[cfg(all(feature = "strict", not(feature = "fast")))]
260    #[inline]
261    #[must_use]
262    pub fn powf(self, exp: crate::D<crate::int::types::Int<2>, SCALE>) -> Self {
263        self.powf_strict(exp)
264    }
265
266    /// Returns the square root of `self` (strict integer-only stub).
267    ///
268    /// IEEE 754 mandates that `f64::sqrt` is correctly-rounded
269    /// (round-to-nearest, ties-to-even). Combined with the deterministic
270    /// `to_f64` / `from_f64` round-trip, this makes
271    /// `D38::sqrt` bit-deterministic: the same input produces the same
272    /// output bit-pattern on every IEEE-754-conformant platform.
273    ///
274    /// Negative inputs produce a NaN from `f64::sqrt`, which
275    /// [`Self::from_f64`] maps to `ZERO` per the saturate-vs-error
276    /// policy. No panic is raised for negative inputs.
277    ///
278    /// # Precision
279    ///
280    /// Strict: all arithmetic is integer-only; result is bit-exact.
281    ///
282    /// # Examples
283    ///
284    /// ```ignore
285    /// use decimal_scaled::D38s12;
286    /// assert_eq!(D38s12::ZERO.sqrt(), D38s12::ZERO);
287    /// // f64::sqrt(1.0) == 1.0 exactly, so the result is bit-exact.
288    /// assert_eq!(D38s12::ONE.sqrt(), D38s12::ONE);
289    /// ```
290    #[inline]
291    #[must_use]
292    pub fn sqrt_strict(self) -> Self {
293        self.sqrt_strict_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
294    }
295
296    /// Square root under the supplied rounding mode.
297    ///
298    /// Negative inputs saturate to [`Self::ZERO`] regardless of mode,
299    /// matching the f64-bridge policy.
300    ///
301    /// Body delegates to `policy::sqrt::SqrtPolicy::sqrt_impl`,
302    /// which for D38 selects the `mg_divide_d38` width-override kernel.
303    #[inline]
304    #[must_use]
305    pub fn sqrt_strict_with(self, mode: crate::support::rounding::RoundingMode) -> Self {
306        Self(crate::policy::sqrt::dispatch::<_, SCALE>(self.0, mode))
307    }
308
309    /// Returns the square root of `self`.
310    ///
311    /// With the `strict` feature enabled this is the integer-only,
312    /// correctly-rounded [`Self::sqrt_strict`]; without it, the
313    /// f64-bridge form.
314    #[cfg(all(feature = "strict", not(feature = "fast")))]
315    #[inline]
316    #[must_use]
317    pub fn sqrt(self) -> Self {
318        self.sqrt_strict()
319    }
320
321    /// Returns the cube root of `self`.
322    ///
323    /// With the `strict` feature enabled this is the integer-only
324    /// [`Self::cbrt_strict`]; without it, the f64-bridge form.
325    #[cfg(all(feature = "strict", not(feature = "fast")))]
326    #[inline]
327    #[must_use]
328    pub fn cbrt(self) -> Self {
329        self.cbrt_strict()
330    }
331
332    /// Cube root of `self`. Defined for all reals — the sign of the
333    /// input is preserved (`cbrt(-8) = -2`).
334    ///
335    /// # Algorithm
336    ///
337    /// For a `D38<SCALE>` with raw storage `r`, the raw storage of the
338    /// cube root is
339    ///
340    /// round( cbrt(r / 10^SCALE) · 10^SCALE )
341    /// = round( cbrt(r · 10^(2·SCALE)) ).
342    ///
343    /// `r · 10^(2·SCALE)` is formed exactly as a 384-bit value and its
344    /// integer cube root is computed exactly, so the result is the
345    /// exact cube root correctly rounded to the type's last place
346    /// (within 0.5 ULP — the IEEE-754 round-to-nearest result).
347    ///
348    /// # Precision
349    ///
350    /// Strict: integer-only; correctly rounded.
351    #[inline]
352    #[must_use]
353    pub fn cbrt_strict(self) -> Self {
354        self.cbrt_strict_with(crate::support::rounding::DEFAULT_ROUNDING_MODE)
355    }
356
357    /// Cube root under the supplied rounding mode. The sign of the
358    /// input is preserved; `Floor` / `Ceiling` resolve direction
359    /// relative to the signed result.
360    ///
361    /// Body delegates to `policy::cbrt::CbrtPolicy::cbrt_impl`.
362    #[inline]
363    #[must_use]
364    pub fn cbrt_strict_with(self, mode: crate::support::rounding::RoundingMode) -> Self {
365        Self(crate::policy::cbrt::dispatch::<_, SCALE>(self.0, mode))
366    }
367
368    /// Returns `sqrt(self^2 + other^2)` without intermediate overflow,
369    /// computed integer-only via the correctly-rounded
370    /// [`Self::sqrt_strict`]. Same scale-trick algorithm as the
371    /// f64-bridge [`Self::hypot`]; available in `no_std`.
372    ///
373    /// Always available, regardless of the `strict` feature.
374    #[inline]
375    #[must_use]
376    pub fn hypot_strict(self, other: Self) -> Self {
377        self.hypot_strict_with(other, crate::support::rounding::DEFAULT_ROUNDING_MODE)
378    }
379
380    /// Hypot under the supplied rounding mode. The mode applies to the
381    /// inner square root; the surrounding adds and multiplies are
382    /// exact-or-truncating per the operator path's own contract.
383    ///
384    /// Body delegates to `policy::hypot::HypotPolicy::hypot_impl`.
385    #[inline]
386    #[must_use]
387    pub fn hypot_strict_with(
388        self,
389        other: Self,
390        mode: crate::support::rounding::RoundingMode,
391    ) -> Self {
392        Self(crate::policy::hypot::dispatch::<_, SCALE>(self.0, other.0, mode))
393    }
394
395    /// Returns `sqrt(self^2 + other^2)` without intermediate overflow.
396    ///
397    /// With the `strict` feature enabled this is the integer-only
398    /// [`Self::hypot_strict`]; without it, the f64-bridge form.
399    #[cfg(all(feature = "strict", not(feature = "fast")))]
400    #[inline]
401    #[must_use]
402    pub fn hypot(self, other: Self) -> Self {
403        self.hypot_strict(other)
404    }
405
406    // Overflow-variant family for pow.
407
408    /// Returns `Some(self^exp)`, or `None` if any multiplication step
409    /// overflows `i128`.
410    ///
411    /// Walks the same square-and-multiply as [`Self::pow`] but uses
412    /// `mul_div_pow10` (which returns `Option<i128>`) at each step.
413    /// The first `None` short-circuits to a `None` return.
414    ///
415    /// # Precision
416    ///
417    /// Strict: all arithmetic is integer-only; result is bit-exact.
418    ///
419    /// # Examples
420    ///
421    /// ```ignore
422    /// use decimal_scaled::D38s12;
423    /// // MAX^2 overflows.
424    /// assert!(D38s12::MAX.checked_pow(2).is_none());
425    /// // Any power of ONE is ONE.
426    /// assert_eq!(D38s12::ONE.checked_pow(1_000_000), Some(D38s12::ONE));
427    /// ```
428    #[inline]
429    #[must_use]
430    pub fn checked_pow(self, exp: u32) -> Option<Self> {
431        let mut acc = Self::ONE;
432        let mut base = self;
433        let mut e = exp;
434        while e > 0 {
435            if e & 1 == 1 {
436                acc = acc.checked_mul(base)?;
437            }
438            e >>= 1;
439            if e > 0 {
440                base = base.checked_mul(base)?;
441            }
442        }
443        Some(acc)
444    }
445
446    /// Returns `self^exp`, wrapping two's-complement on overflow at
447    /// every multiplication step.
448    ///
449    /// Follows the same square-and-multiply structure as [`Self::pow`].
450    /// When a step overflows `mul_div_pow10`, the fallback is
451    /// `wrapping_mul` followed by `wrapping_div` of the scale
452    /// multiplier. The exact wrap pattern is deterministic and
453    /// reproducible but is not otherwise specified.
454    ///
455    /// # Precision
456    ///
457    /// Strict: all arithmetic is integer-only; result is bit-exact.
458    ///
459    /// # Examples
460    ///
461    /// ```ignore
462    /// use decimal_scaled::D38s12;
463    /// // ONE^N never overflows and returns ONE.
464    /// assert_eq!(D38s12::ONE.wrapping_pow(1_000_000), D38s12::ONE);
465    /// // MAX^2 wraps to a deterministic but unspecified value.
466    /// let _ = D38s12::MAX.wrapping_pow(2);
467    /// ```
468    #[inline]
469    #[must_use]
470    pub fn wrapping_pow(self, exp: u32) -> Self {
471        let mut acc = Self::ONE;
472        let mut base = self;
473        let mut e = exp;
474        while e > 0 {
475            if e & 1 == 1 {
476                acc = acc.wrapping_mul(base);
477            }
478            e >>= 1;
479            if e > 0 {
480                base = base.wrapping_mul(base);
481            }
482        }
483        acc
484    }
485
486    /// Returns `self^exp`, clamping to `D38::MAX` or `D38::MIN` on
487    /// overflow at any step.
488    ///
489    /// On the first step that overflows, the result is clamped based on
490    /// the sign of the mathematical result: positive overflows clamp to
491    /// `MAX`, negative overflows clamp to `MIN`. The sign of the result
492    /// is determined by `self.signum()` and whether `exp` is odd.
493    ///
494    /// `exp = 0` always returns `ONE` before entering the loop.
495    ///
496    /// # Precision
497    ///
498    /// Strict: all arithmetic is integer-only; result is bit-exact.
499    ///
500    /// # Examples
501    ///
502    /// ```ignore
503    /// use decimal_scaled::D38s12;
504    /// assert_eq!(D38s12::MAX.saturating_pow(2), D38s12::MAX);
505    /// assert_eq!(D38s12::ONE.saturating_pow(1_000_000), D38s12::ONE);
506    /// ```
507    #[inline]
508    #[must_use]
509    pub fn saturating_pow(self, exp: u32) -> Self {
510        // exp == 0: result is ONE by convention.
511        if exp == 0 {
512            return Self::ONE;
513        }
514        let mut acc = Self::ONE;
515        let mut base = self;
516        let mut e = exp;
517        // The final result is negative iff the base is negative and exp is odd.
518        let result_negative_if_overflow = self.is_negative() && (exp & 1) == 1;
519        while e > 0 {
520            if e & 1 == 1 {
521                match acc.checked_mul(base) {
522                    Some(q) => acc = q,
523                    None => {
524                        return if result_negative_if_overflow {
525                            Self::MIN
526                        } else {
527                            Self::MAX
528                        };
529                    }
530                }
531            }
532            e >>= 1;
533            if e > 0 {
534                match base.checked_mul(base) {
535                    Some(q) => base = q,
536                    None => {
537                        // base*base is non-negative (squared); clamp by the
538                        // sign of the would-be final result.
539                        return if result_negative_if_overflow {
540                            Self::MIN
541                        } else {
542                            Self::MAX
543                        };
544                    }
545                }
546            }
547        }
548        acc
549    }
550
551    /// Returns `(self^exp, overflowed)`.
552    ///
553    /// `overflowed` is `true` if any multiplication step overflowed
554    /// `i128`. The returned value is the wrapping form (matches
555    /// [`Self::wrapping_pow`]).
556    ///
557    /// # Precision
558    ///
559    /// Strict: all arithmetic is integer-only; result is bit-exact.
560    ///
561    /// # Examples
562    ///
563    /// ```ignore
564    /// use decimal_scaled::D38s12;
565    /// let (_value, overflowed) = D38s12::MAX.overflowing_pow(2);
566    /// assert!(overflowed);
567    /// let (value, overflowed) = D38s12::ONE.overflowing_pow(5);
568    /// assert!(!overflowed);
569    /// assert_eq!(value, D38s12::ONE);
570    /// ```
571    #[inline]
572    #[must_use]
573    pub fn overflowing_pow(self, exp: u32) -> (Self, bool) {
574        let mut acc = Self::ONE;
575        let mut base = self;
576        let mut e = exp;
577        let mut overflowed = false;
578        while e > 0 {
579            if e & 1 == 1 {
580                let (q, o) = acc.overflowing_mul(base);
581                acc = q;
582                overflowed |= o;
583            }
584            e >>= 1;
585            if e > 0 {
586                let (q, o) = base.overflowing_mul(base);
587                base = q;
588                overflowed |= o;
589            }
590        }
591        (acc, overflowed)
592    }
593}
594
595#[cfg(test)]
596mod tests {
597    /// Strict `sqrt` is correctly rounded: for the raw result `q`, the
598    /// scaled radicand `N = r · 10^SCALE` must satisfy
599    /// `(q − 0.5)² ≤ N ≤ (q + 0.5)²`, i.e. `q` is the exact square root
600    /// rounded to nearest. Checked exactly in 256-bit integer space
601    /// across several scales and magnitudes.
602    #[test]
603    fn strict_sqrt_is_correctly_rounded() {
604        // (q - 0.5)^2 = q^2 - q + 0.25 → lower bound  N ≥ q^2 - q + 1 (ints, when q>0)
605        // (q + 0.5)^2 = q^2 + q + 0.25 → upper bound  N ≤ q^2 + q
606        // So a correctly-rounded q satisfies q^2 - q < N ≤ q^2 + q  (q>0),
607        // or N == 0 when q == 0.
608        fn check<const S: u32>(raw: i128) {
609            let x = crate::D::<crate::int::types::Int<2>, S>::from_bits(crate::int::types::Int::<2>::from_i128(raw));
610            let q = x.sqrt_strict().to_bits().as_i128();
611            assert!(q >= 0, "sqrt result must be non-negative");
612            // N = raw · 10^S as 256-bit; q is small enough that q^2 fits 256-bit.
613            let mult = 10u128.pow(S);
614            let (n_hi, n_lo) = crate::algos::support::mg_divide::mul_u128_to_u256(raw as u128, mult);
615            let (qsq_hi, qsq_lo) = crate::algos::support::mg_divide::mul_u128_to_u256(q as u128, q as u128);
616            // lower: N > q^2 - q ⇔   N + q > q^2   (q ≥ 0)
617            // upper: N ≤ q^2 + q
618            let q_u = q as u128;
619            // q^2 + q (256-bit)
620            let (uphi, uplo) = {
621                let (lo, c) = qsq_lo.overflowing_add(q_u);
622                (qsq_hi + c as u128, lo)
623            };
624            // N ≤ q^2 + q ?
625            let n_le_upper = n_hi < uphi || (n_hi == uphi && n_lo <= uplo);
626            assert!(n_le_upper, "sqrt({raw} @ s{S}) = {q}: N exceeds (q+0.5)^2");
627            if q > 0 {
628                // N + q (256-bit)
629                let (nphi, nplo) = {
630                    let (lo, c) = n_lo.overflowing_add(q_u);
631                    (n_hi + c as u128, lo)
632                };
633                // N + q > q^2 ?
634                let above_lower = nphi > qsq_hi || (nphi == qsq_hi && nplo > qsq_lo);
635                assert!(above_lower, "sqrt({raw} @ s{S}) = {q}: N below (q-0.5)^2");
636            }
637        }
638        for &raw in &[
639            1_i128,
640            2,
641            3,
642            4,
643            5,
644            999_999_999_999,
645            1_000_000_000_000,
646            1_500_000_000_000,
647            123_456_789_012_345,
648            i128::MAX,
649            i128::MAX / 7,
650        ] {
651            check::<0>(raw);
652            check::<6>(raw);
653            check::<12>(raw);
654            check::<19>(raw);
655        }
656        // High-scale cases where the radicand approaches the 256-bit cap.
657        for &raw in &[1_i128, 2, 17, i128::MAX, i128::MAX / 3] {
658            check::<38>(raw);
659        }
660    }
661    /// Strict `cbrt` is correctly rounded: for the raw result `q`, the
662    /// scaled radicand `N = |r| · 10^(2·SCALE)` must satisfy
663    /// `(2q − 1)³ < 8·N ≤ (2q + 1)³`, i.e. `q` is the exact cube root
664    /// rounded to nearest. Checked exactly in 384-bit integer space.
665    #[test]
666    fn strict_cbrt_is_correctly_rounded() {
667        // q correctly rounded ⇔  q − 0.5 < cbrt(N) ≤ q + 0.5
668        // ⇔  (2q − 1)³ < 8N ≤ (2q + 1)³.
669        // 384-bit comparison via num-bigint-free manual limbs would be
670        // verbose, so this check leans on the i256 dev-dependency to
671        // hold the 384-bit cubes (i256 is already a dev-dependency).
672        use i256::U256;
673        fn check<const S: u32>(raw: i128) {
674            let x = crate::D::<crate::int::types::Int<2>, S>::from_bits(crate::int::types::Int::<2>::from_i128(raw));
675            let q = x.cbrt_strict().to_bits().as_i128();
676            // Sign must match the input.
677            assert_eq!(q.signum(), raw.signum(), "cbrt sign mismatch");
678            let qa = q.unsigned_abs();
679            let ra = raw.unsigned_abs();
680            // N = |r| · 10^(2S). 2S ≤ 76, so 10^(2S) needs U256; the
681            // product needs more than 256 bits at high S, so cap the
682            // scales exercised here to keep the check in U256 range.
683            // (The 384-bit path itself is exercised across all scales by
684            // the round-trip tests; this exact check covers S ≤ 25.)
685            let m = U256::from(10u8).pow(2 * S);
686            let n = U256::from(ra) * m;
687            let eight_n = n << 3;
688            let two_q = U256::from(qa) * U256::from(2u8);
689            let upper = {
690                let t = two_q + U256::from(1u8);
691                t * t * t
692            };
693            assert!(
694                eight_n <= upper,
695                "cbrt({raw} @ s{S}) = {q}: 8N exceeds (2q+1)^3"
696            );
697            if qa > 0 {
698                let t = two_q - U256::from(1u8);
699                let lower = t * t * t;
700                assert!(
701                    eight_n > lower,
702                    "cbrt({raw} @ s{S}) = {q}: 8N at/below (2q-1)^3"
703                );
704            }
705        }
706        for &raw in &[
707            1_i128,
708            2,
709            7,
710            8,
711            9,
712            26,
713            27,
714            28,
715            999_999_999_999,
716            1_000_000_000_000,
717            123_456_789_012_345,
718            -8,
719            -27,
720            -1_000_000_000_000,
721        ] {
722            check::<0>(raw);
723            check::<6>(raw);
724            check::<12>(raw);
725        }
726        // Larger magnitudes at low scale (still within the U256 check).
727        for &raw in &[i128::MAX, i128::MIN + 1, i128::MAX / 11] {
728            check::<0>(raw);
729            check::<2>(raw);
730        }
731    }
732}