use crate::Fixed;
const FRAC_BITS: u32 = 16;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct Wide(i128);
impl Wide {
pub const ZERO: Self = Self(0);
#[must_use]
pub const fn to_bits(self) -> i128 {
self.0
}
#[must_use]
pub const fn signum(self) -> i32 {
self.0.signum() as i32
}
#[must_use]
pub fn sqrt(self) -> Fixed {
assert!(self.0 >= 0, "Wide::sqrt of a negative value");
self.checked_sqrt().unwrap_or(Fixed::ZERO)
}
#[must_use]
pub fn checked_sqrt(self) -> Option<Fixed> {
if self.0 < 0 {
return None;
}
#[expect(clippy::cast_sign_loss, reason = "non-negative by the check above")]
let root = (self.0 as u128).isqrt();
if root > i64::MAX as u128 {
crate::saturation::record();
return Some(Fixed::from_bits(i64::MAX));
}
#[expect(
clippy::cast_possible_truncation,
reason = "bounded by the saturation check immediately above"
)]
let narrowed = root as i64;
Some(Fixed::from_bits(narrowed))
}
#[must_use]
pub const fn checked_narrow(self) -> Option<Fixed> {
let rounded = round_shift(self.0);
if rounded > i64::MAX as i128 || rounded < i64::MIN as i128 {
None
} else {
#[expect(
clippy::cast_possible_truncation,
reason = "the branches above establish the value is in range"
)]
let narrowed = rounded as i64;
Some(Fixed::from_bits(narrowed))
}
}
}
const fn round_shift(value: i128) -> i128 {
let half = 1i128 << (FRAC_BITS - 1);
if value >= 0 {
(value + half) >> FRAC_BITS
} else {
-((-value + half) >> FRAC_BITS)
}
}
impl Fixed {
#[must_use]
pub const fn wide_mul(self, other: Self) -> Wide {
Wide(self.to_bits() as i128 * other.to_bits() as i128)
}
}
fn counted(value: i128, saturated: bool) -> Wide {
if saturated {
crate::saturation::record();
}
Wide(value)
}
impl core::ops::Add for Wide {
type Output = Self;
fn add(self, other: Self) -> Self {
match self.0.checked_add(other.0) {
Some(value) => counted(value, false),
None => counted(if self.0 > 0 { i128::MAX } else { i128::MIN }, true),
}
}
}
impl core::ops::Sub for Wide {
type Output = Self;
fn sub(self, other: Self) -> Self {
match self.0.checked_sub(other.0) {
Some(value) => counted(value, false),
None => counted(if self.0 > 0 { i128::MAX } else { i128::MIN }, true),
}
}
}
impl core::ops::Neg for Wide {
type Output = Self;
fn neg(self) -> Self {
match self.0.checked_neg() {
Some(value) => counted(value, false),
None => counted(i128::MAX, true),
}
}
}
#[cfg(test)]
mod tests {
use super::Wide;
use crate::Fixed;
#[test]
fn a_product_of_two_extremes_does_not_overflow() {
let wide = Fixed::MAX.wide_mul(Fixed::MAX);
assert!(wide.to_bits() > 0, "the product stayed positive");
assert_eq!(wide.checked_narrow(), None, "and it does not fit a Fixed");
assert_eq!(Fixed::MIN.wide_mul(Fixed::MAX).signum(), -1);
}
#[test]
fn squaring_then_rooting_returns_the_value() {
for units in [1i32, 2, 7, 100, 4096, 1_000_000] {
let value = Fixed::from_int(units);
let root = value.wide_mul(value).sqrt();
assert_eq!(root, value, "sqrt of {units} squared should be {units}");
}
}
#[test]
fn rooting_works_at_magnitudes_the_narrow_path_cannot_reach() {
let big = Fixed::from_int(100_000_000);
assert_eq!(
big.wide_mul(big).checked_narrow(),
None,
"square does not fit"
);
assert_eq!(big.wide_mul(big).sqrt(), big);
}
#[test]
fn narrowing_rounds_the_way_the_scalar_multiply_does() {
for (a, b) in [(3, 7), (-3, 7), (3, -7), (-3, -7), (1, 3), (-1, 3)] {
let x = Fixed::from_ratio(a, 4);
let y = Fixed::from_ratio(b, 8);
assert_eq!(
x.wide_mul(y).checked_narrow(),
Some(x.saturating_mul(y)),
"wide and narrow multiply disagreed on {a}/4 * {b}/8"
);
}
}
#[test]
fn a_wide_overflow_is_counted_like_a_narrow_one() {
let huge = Fixed::MAX.wide_mul(Fixed::MAX);
let before = crate::saturations();
let _ = huge + huge + huge;
assert!(
crate::saturations().0 > before.0,
"a wide sum that overflowed reported nothing"
);
let small = Fixed::ONE.wide_mul(Fixed::ONE);
let quiet = crate::saturations();
let _ = small + small - small;
assert_eq!(crate::saturations(), quiet);
let very_negative = Fixed::MIN.wide_mul(Fixed::MAX);
let floored = very_negative + very_negative + very_negative;
let before_sub = crate::saturations();
let _ = floored - huge;
assert!(
crate::saturations().0 > before_sub.0,
"a wide subtraction that overflowed reported nothing"
);
let before_neg = crate::saturations();
let _ = -floored;
assert!(
crate::saturations().0 > before_neg.0,
"negating the bottom of the range reported nothing"
);
}
#[test]
fn a_negative_value_has_no_root_and_says_so() {
let negative = Fixed::from_int(-1).wide_mul(Fixed::from_int(1));
assert_eq!(negative.checked_sqrt(), None);
assert_eq!(negative.signum(), -1);
assert_eq!(Wide::ZERO.signum(), 0);
assert_eq!(Wide::ZERO.sqrt(), Fixed::ZERO);
}
#[test]
#[should_panic(expected = "Wide::sqrt of a negative value")]
fn the_asserting_root_refuses_a_negative() {
let _ = Fixed::from_int(-1).wide_mul(Fixed::ONE).sqrt();
}
#[test]
fn wide_values_add_subtract_and_order() {
let two = Fixed::from_int(2);
let three = Fixed::from_int(3);
let six = two.wide_mul(three);
let four = two.wide_mul(two);
assert!(six > four);
assert_eq!((six - four).checked_narrow(), Some(Fixed::from_int(2)));
assert_eq!((four + four).checked_narrow(), Some(Fixed::from_int(8)));
assert_eq!((-six).signum(), -1);
assert_eq!(six.max(four), six);
}
}