use crate::decimal::{DEFAULT_DIVISION_SCALE, Decimal, MAX_SUPPORTED_SCALE};
use ethnum::I256;
use std::{
iter::{Product, Sum},
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign},
};
impl Decimal {
fn checked_add_impl(self, rhs: Self) -> Option<Self> {
let target_scale = self.scale.max(rhs.scale);
let lhs = Self::align_to_scale(self.mantissa, self.scale, target_scale)?;
let rhs = Self::align_to_scale(rhs.mantissa, rhs.scale, target_scale)?;
Self::fit_wide_mantissa(lhs.checked_add(rhs)?, target_scale)
}
#[must_use]
pub fn checked_add(self, rhs: Self) -> Option<Self> {
self.checked_add_impl(rhs)
}
#[must_use]
pub fn checked_sub(self, rhs: Self) -> Option<Self> {
let target_scale = self.scale.max(rhs.scale);
let lhs = Self::align_to_scale(self.mantissa, self.scale, target_scale)?;
let rhs = Self::align_to_scale(rhs.mantissa, rhs.scale, target_scale)?;
Self::fit_wide_mantissa(lhs.checked_sub(rhs)?, target_scale)
}
#[must_use]
pub fn checked_mul(self, rhs: Self) -> Option<Self> {
self.checked_mul_impl(rhs)
}
#[must_use]
pub fn checked_div(self, rhs: Self) -> Option<Self> {
self.checked_div_impl(rhs)
}
fn checked_mul_impl(self, rhs: Self) -> Option<Self> {
let lhs = self.normalize();
let rhs = rhs.normalize();
let scale = lhs.scale.checked_add(rhs.scale)?;
let product = I256::from(lhs.mantissa).checked_mul(I256::from(rhs.mantissa))?;
Self::fit_wide_mantissa(product, scale)
}
fn fit_wide_mantissa(mantissa: I256, scale: u32) -> Option<Self> {
let mut result_scale = scale.min(MAX_SUPPORTED_SCALE);
let mut divisor = I256::new(10).checked_pow(scale - result_scale)?;
loop {
if let Some(mantissa) = Self::div_round_half_away_from_zero(mantissa, divisor) {
return Some(Self {
mantissa,
scale: result_scale,
});
}
if result_scale == 0 {
return None;
}
result_scale -= 1;
divisor = divisor.checked_mul(I256::new(10))?;
}
}
fn checked_div_impl(self, rhs: Self) -> Option<Self> {
if rhs.is_zero() {
return None;
}
let lhs = self.normalize();
let rhs = rhs.normalize();
let mut target_scale = DEFAULT_DIVISION_SCALE;
loop {
if let Some((numerator, denominator)) = Self::division_operands(lhs, rhs, target_scale)
&& let Some(mantissa) = Self::div_round_half_away_from_zero(numerator, denominator)
{
return Some(
Self {
mantissa,
scale: target_scale,
}
.normalize(),
);
}
if target_scale == 0 {
return None;
}
target_scale -= 1;
}
}
fn checked_rem_impl(self, rhs: Self) -> Option<Self> {
if rhs.is_zero() {
return None;
}
let target_scale = self.scale.max(rhs.scale);
let lhs = Self::align_to_scale(self.mantissa, self.scale, target_scale)?;
let rhs = Self::align_to_scale(rhs.mantissa, rhs.scale, target_scale)?;
Some(Self {
mantissa: i128::try_from(lhs.checked_rem(rhs)?).ok()?,
scale: target_scale,
})
}
#[must_use]
pub const fn round_dp(&self, dp: u32) -> Self {
if self.scale <= dp {
return *self;
}
let diff = self.scale - dp;
let Some(divisor) = Self::checked_pow10(diff) else {
return *self;
};
let quotient = self.mantissa / divisor;
let remainder = self.mantissa % divisor;
let should_round = remainder.unsigned_abs() >= divisor.unsigned_abs() / 2;
let rounded = if should_round {
if self.mantissa.is_negative() {
quotient.saturating_sub(1)
} else {
quotient.saturating_add(1)
}
} else {
quotient
};
Self {
mantissa: rounded,
scale: dp,
}
}
#[must_use]
pub const fn trunc_dp(&self, dp: u32) -> Self {
if self.scale <= dp {
return *self;
}
let diff = self.scale - dp;
let Some(divisor) = Self::checked_pow10(diff) else {
return *self;
};
Self {
mantissa: self.mantissa / divisor,
scale: dp,
}
}
#[must_use]
pub const fn abs(&self) -> Self {
Self {
mantissa: self.mantissa.saturating_abs(),
scale: self.scale,
}
}
#[must_use]
pub const fn floor_dp0(&self) -> Self {
if self.scale == 0 {
return *self;
}
let Some(divisor) = Self::checked_pow10(self.scale) else {
return *self;
};
let quotient = self.mantissa / divisor;
let remainder = self.mantissa % divisor;
let integer = if self.mantissa.is_negative() && remainder != 0 {
quotient.saturating_sub(1)
} else {
quotient
};
Self {
mantissa: integer,
scale: 0,
}
}
#[must_use]
pub const fn ceil_dp0(&self) -> Self {
if self.scale == 0 {
return *self;
}
let Some(divisor) = Self::checked_pow10(self.scale) else {
return *self;
};
let quotient = self.mantissa / divisor;
let remainder = self.mantissa % divisor;
let integer = if self.mantissa.is_positive() && remainder != 0 {
quotient.saturating_add(1)
} else {
quotient
};
Self {
mantissa: integer,
scale: 0,
}
}
#[must_use]
pub fn saturating_add(self, rhs: Self) -> Self {
self.checked_add_impl(rhs)
.unwrap_or_else(|| Self::saturating_extreme(self.is_sign_negative()))
}
#[must_use]
pub fn saturating_sub(self, rhs: Self) -> Self {
self.checked_sub(rhs)
.unwrap_or_else(|| Self::saturating_extreme(self < rhs))
}
#[must_use]
pub fn checked_rem(self, rhs: Self) -> Option<Self> {
self.checked_rem_impl(rhs)
}
#[must_use]
pub const fn checked_abs(&self) -> Option<Self> {
let Some(mantissa) = self.mantissa.checked_abs() else {
return None;
};
Some(Self {
mantissa,
scale: self.scale,
})
}
#[must_use]
pub fn powu(&self, exp: u64) -> Self {
if exp == 0 {
return Self::new(1, 0);
}
let mut base = *self;
let mut power = exp;
let mut acc = Self::new(1, 0);
while power > 0 {
if power & 1 == 1 {
acc *= base;
}
power >>= 1;
if power > 0 {
base = base * base;
}
}
acc
}
#[must_use]
pub fn checked_powu(&self, exp: u64) -> Option<Self> {
if exp == 0 {
return Some(Self::new(1, 0));
}
let mut base = *self;
let mut power = exp;
let mut acc = Self::new(1, 0);
while power > 0 {
if power & 1 == 1 {
acc = acc.checked_mul(base)?;
}
power >>= 1;
if power > 0 {
base = base.checked_mul(base)?;
}
}
Some(acc)
}
fn align_to_scale(mantissa: i128, current_scale: u32, target_scale: u32) -> Option<I256> {
let factor = Self::checked_pow10(target_scale.checked_sub(current_scale)?)?;
I256::from(mantissa).checked_mul(I256::from(factor))
}
fn division_operands(lhs: Self, rhs: Self, target_scale: u32) -> Option<(I256, I256)> {
let exponent = i64::from(target_scale) + i64::from(rhs.scale) - i64::from(lhs.scale);
let factor = I256::new(10).checked_pow(u32::try_from(exponent.unsigned_abs()).ok()?)?;
let lhs = I256::from(lhs.mantissa);
let rhs = I256::from(rhs.mantissa);
if exponent >= 0 {
return Some((lhs.checked_mul(factor)?, rhs));
}
Some((lhs, rhs.checked_mul(factor)?))
}
fn div_round_half_away_from_zero(numerator: I256, denominator: I256) -> Option<i128> {
let quotient = numerator.checked_div(denominator)?;
let remainder = numerator.checked_rem(denominator)?;
if remainder == 0 {
return i128::try_from(quotient).ok();
}
let twice_remainder = remainder.unsigned_abs().checked_mul(2_u8.into())?;
if twice_remainder < denominator.unsigned_abs() {
return i128::try_from(quotient).ok();
}
let rounded = if (numerator < 0) == (denominator < 0) {
quotient.checked_add(I256::new(1))?
} else {
quotient.checked_sub(I256::new(1))?
};
i128::try_from(rounded).ok()
}
}
impl Add for Decimal {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
self.saturating_add(rhs)
}
}
impl AddAssign for Decimal {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
impl Sub for Decimal {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
self.saturating_sub(rhs)
}
}
impl SubAssign for Decimal {
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
}
}
impl Mul for Decimal {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
self.checked_mul_impl(rhs).unwrap_or_else(|| {
Self::saturating_extreme(self.is_sign_negative() != rhs.is_sign_negative())
})
}
}
impl MulAssign for Decimal {
fn mul_assign(&mut self, rhs: Self) {
*self = *self * rhs;
}
}
impl Neg for Decimal {
type Output = Self;
fn neg(self) -> Self::Output {
Self {
mantissa: self.mantissa.saturating_neg(),
scale: self.scale,
}
}
}
impl Product for Decimal {
fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(Self::new_unchecked(1, 0), |acc, value| acc * value)
}
}
impl Div for Decimal {
type Output = Self;
fn div(self, rhs: Self) -> Self::Output {
if rhs.is_zero() {
return Self::ZERO;
}
self.checked_div_impl(rhs).unwrap_or_else(|| {
let negative = self.is_sign_negative() != rhs.is_sign_negative();
Self::saturating_extreme(negative)
})
}
}
impl DivAssign for Decimal {
fn div_assign(&mut self, rhs: Self) {
*self = *self / rhs;
}
}
impl Rem for Decimal {
type Output = Self;
fn rem(self, rhs: Self) -> Self::Output {
self.checked_rem_impl(rhs).unwrap_or(Self::ZERO)
}
}
impl RemAssign for Decimal {
fn rem_assign(&mut self, rhs: Self) {
*self = *self % rhs;
}
}
impl Sum for Decimal {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(Self::ZERO, |acc, value| acc + value)
}
}