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}