diff --git a/src/lib.rs b/src/lib.rs index 77b6dfb..a96aaab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,10 +23,12 @@ pub struct Approximint { impl Approximint { const COEFFICIENT_LIMIT: i32 = 1_000_000_000; + /// The maximum value representable by an Approximint. pub const MAX: Self = Self { ten_power: u32::MAX, coefficient: 999_999_999, }; + /// The minimum value representable by an Approximint. pub const MIN: Self = Self { ten_power: u32::MAX, coefficient: -999_999_999, @@ -145,6 +147,56 @@ impl Approximint { pub fn as_decimal(self) -> DecimalFormatter { DecimalFormatter::from(self) } + + /// Returns the result of raising `self` to the `exponent` power. + /// + /// Rather than aiming for accuracy, this function only attempts two + /// approaches to avoiding overflows when raising to large powers. If an + /// overflow occurs during raising the coefficient to the exponent power, + /// the entire calcualtion will overflow rather than an invalid result being + /// returned. + /// + /// Due to these limitations, this function should be only used to raise to + /// powers that overflows are unlikely to occur. + #[must_use] + #[expect(clippy::cast_possible_truncation)] + pub fn powi(self, exponent: u32) -> Self { + if self.coefficient == 0 { + Self::ZERO + } else if exponent > 0 { + let this = self.maximize_ten_power(); + let ten_power = this.ten_power.checked_pow(exponent); + let coefficient = i128::from(this.coefficient) + .checked_pow(exponent) + .or_else(|| { + let raised = f64::from(this.coefficient) + .powi(i32::try_from(exponent).unwrap_or(i32::MAX)) + as i128; + (raised > i128::MIN && raised < i128::MAX).then_some(raised) + }); + let (Some(coefficient), Some(ten_power)) = (coefficient, ten_power) else { + return Self { + coefficient: this.coefficient.signum() * 999_999_999, + ten_power: u32::MAX, + }; + }; + let mut normalized = coefficient.approximate(); + normalized.ten_power = normalized.ten_power.saturating_add(ten_power); + normalized.normalize_underflow() + } else { + Self::ONE + } + } + + const fn maximize_ten_power(mut self) -> Self { + if self.coefficient != 0 { + while self.coefficient % 10 == 0 { + self.ten_power += 1; + self.coefficient /= 10; + } + } + self + } } impl Neg for Approximint { @@ -173,6 +225,14 @@ impl Add for Approximint { .normalized() } } +impl Add for Approximint { + type Output = Self; + + #[inline] + fn add(self, rhs: i32) -> Self::Output { + self + Self::new(rhs) + } +} impl AddAssign for Approximint { #[inline] @@ -181,6 +241,13 @@ impl AddAssign for Approximint { } } +impl AddAssign for Approximint { + #[inline] + fn add_assign(&mut self, rhs: i32) { + *self += Self::new(rhs); + } +} + impl Sub for Approximint { type Output = Self; @@ -195,6 +262,15 @@ impl Sub for Approximint { } } +impl Sub for Approximint { + type Output = Self; + + #[inline] + fn sub(self, rhs: i32) -> Self::Output { + self - Self::new(rhs) + } +} + impl SubAssign for Approximint { #[inline] fn sub_assign(&mut self, rhs: Self) { @@ -202,6 +278,13 @@ impl SubAssign for Approximint { } } +impl SubAssign for Approximint { + #[inline] + fn sub_assign(&mut self, rhs: i32) { + *self -= Self::new(rhs); + } +} + impl Mul for Approximint { type Output = Self; @@ -235,6 +318,15 @@ impl Mul for Approximint { } } +impl Mul for Approximint { + type Output = Self; + + #[inline] + fn mul(self, rhs: i32) -> Self::Output { + self * Self::new(rhs) + } +} + #[cfg(feature = "std")] impl Mul for Approximint { type Output = Self; @@ -757,7 +849,11 @@ impl<'a> WordFormatter<'a> { impl Display for WordFormatter<'_> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - self.format_info(ScientificInfo::new(self.decimal.num), f) + if self.decimal.num == Approximint::ZERO { + f.write_str("0") + } else { + self.format_info(ScientificInfo::new(self.decimal.num), f) + } } } @@ -937,6 +1033,55 @@ impl Approximate for u128 { } } +impl Approximate for i32 { + #[inline] + fn approximate(self) -> Approximint { + Approximint::new(self) + } +} + +impl Approximate for i64 { + #[inline] + #[expect(clippy::cast_possible_truncation)] + fn approximate(mut self) -> Approximint { + let mut ten_power = 0; + while self >= i64::from(Approximint::COEFFICIENT_LIMIT) { + ten_power += 1; + self /= 10; + } + while self <= i64::from(-Approximint::COEFFICIENT_LIMIT) { + ten_power += 1; + self /= 10; + } + + Approximint { + coefficient: self as i32, + ten_power, + } + } +} + +impl Approximate for i128 { + #[inline] + #[expect(clippy::cast_possible_truncation)] + fn approximate(mut self) -> Approximint { + let mut ten_power = 0; + while self >= i128::from(Approximint::COEFFICIENT_LIMIT) { + ten_power += 1; + self /= 10; + } + while self <= i128::from(-Approximint::COEFFICIENT_LIMIT) { + ten_power += 1; + self /= 10; + } + + Approximint { + coefficient: self as i32, + ten_power, + } + } +} + #[cfg(feature = "std")] impl Approximate for f64 { #[inline] @@ -961,6 +1106,14 @@ impl Approximate for f64 { } } +#[cfg(feature = "std")] +impl From for f64 { + fn from(value: Approximint) -> Self { + let coefficient = f64::from(value.coefficient) / 1_000_000_000.0; + coefficient * 10f64.powi(i32::try_from(value.ten_power).unwrap_or(i32::MAX)) + } +} + #[cfg(feature = "std")] impl Approximate for f32 { #[inline] diff --git a/src/tests.rs b/src/tests.rs index 1944282..2788e37 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -215,3 +215,13 @@ fn debug_output() { "1.23456789e9" ); } + +#[test] +fn powers() { + assert_eq!(Approximint::one_e(3).powi(2), Approximint::one_e(9)); + assert_eq!(Approximint::new(2).powi(20000), Approximint::MAX); + assert_eq!( + (Approximint::one_e(2) * 2).powi(8), + Approximint::new(256) * Approximint::one_e(256) + ); +}