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