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