// #[rustfmt::skip] is being used because `rustfmt` poorly formats `#[doc = concat!(..)]`. See
// https://github.com/rust-lang/rustfmt/issues/5062 for more information.
use crate::{decimal::CalculationResult, ops, Decimal};
use core::ops::{Add, Div, Mul, Rem, Sub};
use num_traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedRem, CheckedSub, Inv};
// Macros and `Decimal` implementations
#[rustfmt::skip]
macro_rules! impl_checked {
($long:literal, $short:literal, $fun:ident, $
impl:ident) => {
#[doc = concat!(
"Checked ",
$long,
". Computes `self ",
$short,
" other`, returning `None` if overflow occurred."
)]
#[inline(always)]
#[must_use]
pub fn $fun(
self, other: Decimal) -> Option<Decimal> {
match ops::$
impl(&
self, &other) {
CalculationResult::Ok(result) => Some(result),
_ => None,
}
}
};
}
#[rustfmt::skip]
macro_rules! impl_saturating {
($long:literal, $short:literal, $fun:ident, $
impl:ident, $cmp:ident) => {
#[doc = concat!(
"Saturating ",
$long,
". Computes `self ",
$short,
" other`, saturating at the relevant upper or lower boundary.",
)]
#[inline(always)]
#[must_use]
pub fn $fun(
self, other: Decimal) -> Decimal {
if let Some(elem) =
self.$
impl(other) {
elem
}
else {
$cmp(&
self, &other)
}
}
};
}
macro_rules! impl_checked_and_saturating {
(
$op_long:literal,
$op_short:literal,
$checked_fun:ident,
$checked_impl:ident,
$saturating_fun:ident,
$saturating_cmp:ident
) => {
impl_checked!($op_long, $op_short, $checked_fun, $checked_impl);
impl_saturating!(
$op_long,
$op_short,
$saturating_fun,
$checked_fun,
$saturating_cmp
);
};
}
impl Decimal {
impl_checked_and_saturating!(
"addition",
"+",
checked_add,
add_impl,
saturating_add,
if_a_is_positive_then_max
);
impl_checked_and_saturating!(
"multiplication",
"*",
checked_mul,
mul_impl,
saturating_mul,
if_xnor_then_max
);
impl_checked_and_saturating!(
"subtraction",
"-",
checked_sub,
sub_impl,
saturating_sub,
if_a_is_positive_then_max
);
impl_checked!(
"division",
"/", checked_div, div_impl);
impl_checked!(
"remainder",
"%", checked_rem, rem_impl);
}
// Macros and trait implementations
macro_rules! forward_all_binop {
(
impl $imp:ident
for $res:ty, $method:ident) => {
forward_val_val_binop!(
impl $imp
for $res, $method);
forward_ref_val_binop!(
impl $imp
for $res, $method);
forward_val_ref_binop!(
impl $imp
for $res, $method);
};
}
macro_rules! forward_ref_val_binop {
(
impl $imp:ident
for $res:ty, $method:ident) => {
impl<
'a> $imp<$res> for &'a $res {
type Output = $res;
#[inline]
fn $method(
self, other: $res) -> $res {
self.$method(&other)
}
}
};
}
macro_rules! forward_val_ref_binop {
(
impl $imp:ident
for $res:ty, $method:ident) => {
impl<
'a> $imp<&'a $res>
for $res {
type Output = $res;
#[inline]
fn $method(
self, other: &$res) -> $res {
(&
self).$method(other)
}
}
};
}
macro_rules! forward_val_val_binop {
(
impl $imp:ident
for $res:ty, $method:ident) => {
impl $imp<$res>
for $res {
type Output = $res;
#[inline]
fn $method(
self, other: $res) -> $res {
(&
self).$method(&other)
}
}
};
}
forward_all_binop!(
impl Add
for Decimal, add);
impl<
'a, 'b> Add<&
'b Decimal> for &'a Decimal {
type Output = Decimal;
#[inline(always)]
fn add(
self, other: &Decimal) -> Decimal {
match ops::add_impl(
self, other) {
CalculationResult::Ok(sum) => sum,
_ => panic!(
"Addition overflowed"),
}
}
}
impl CheckedAdd
for Decimal {
#[inline]
fn checked_add(&
self, v: &Decimal) -> Option<Decimal> {
Decimal::checked_add(*
self, *v)
}
}
impl CheckedSub
for Decimal {
#[inline]
fn checked_sub(&
self, v: &Decimal) -> Option<Decimal> {
Decimal::checked_sub(*
self, *v)
}
}
impl CheckedMul
for Decimal {
#[inline]
fn checked_mul(&
self, v: &Decimal) -> Option<Decimal> {
Decimal::checked_mul(*
self, *v)
}
}
impl CheckedDiv
for Decimal {
#[inline]
fn checked_div(&
self, v: &Decimal) -> Option<Decimal> {
Decimal::checked_div(*
self, *v)
}
}
impl CheckedRem
for Decimal {
#[inline]
fn checked_rem(&
self, v: &Decimal) -> Option<Decimal> {
Decimal::checked_rem(*
self, *v)
}
}
impl Inv
for Decimal {
type Output =
Self;
#[inline]
fn inv(
self) ->
Self {
Decimal::ONE /
self
}
}
forward_all_binop!(
impl Div
for Decimal, div);
impl<
'a, 'b> Div<&
'b Decimal> for &'a Decimal {
type Output = Decimal;
#[inline]
fn div(
self, other: &Decimal) -> Decimal {
match ops::div_impl(
self, other) {
CalculationResult::Ok(quot) => quot,
CalculationResult::Overflow => panic!(
"Division overflowed"),
CalculationResult::DivByZero => panic!(
"Division by zero"),
}
}
}
forward_all_binop!(
impl Mul
for Decimal, mul);
impl<
'a, 'b> Mul<&
'b Decimal> for &'a Decimal {
type Output = Decimal;
#[inline]
fn mul(
self, other: &Decimal) -> Decimal {
match ops::mul_impl(
self, other) {
CalculationResult::Ok(prod) => prod,
_ => panic!(
"Multiplication overflowed"),
}
}
}
forward_all_binop!(
impl Rem
for Decimal, rem);
impl<
'a, 'b> Rem<&
'b Decimal> for &'a Decimal {
type Output = Decimal;
#[inline]
fn rem(
self, other: &Decimal) -> Decimal {
match ops::rem_impl(
self, other) {
CalculationResult::Ok(rem) => rem,
CalculationResult::Overflow => panic!(
"Division overflowed"),
CalculationResult::DivByZero => panic!(
"Division by zero"),
}
}
}
forward_all_binop!(
impl Sub
for Decimal, sub);
impl<
'a, 'b> Sub<&
'b Decimal> for &'a Decimal {
type Output = Decimal;
#[inline(always)]
fn sub(
self, other: &Decimal) -> Decimal {
match ops::sub_impl(
self, other) {
CalculationResult::Ok(sum) => sum,
_ => panic!(
"Subtraction overflowed"),
}
}
}
// This function signature is expected by `impl_saturating`, thus the reason of `_b`.
#[inline(always)]
const fn if_a_is_positive_then_max(a: &Decimal, _b: &Decimal) -> Decimal {
if a.is_sign_positive() {
Decimal::MAX
}
else {
Decimal::MIN
}
}
// Used by saturating multiplications.
//
// If the `a` and `b` combination represents a XNOR bit operation, returns MAX. Otherwise,
// returns MIN.
#[inline(always)]
const fn if_xnor_then_max(a: &Decimal, b: &Decimal) -> Decimal {
match (a.is_sign_positive(), b.is_sign_positive()) {
(
true,
true) => Decimal::MAX,
(
true,
false) => Decimal::MIN,
(
false,
true) => Decimal::MIN,
(
false,
false) => Decimal::MAX,
}
}
#[cfg(test)]
mod tests {
use crate::Decimal;
#[test]
fn checked_methods_have_correct_output() {
assert_eq!(Decimal::MAX.checked_add(Decimal::MAX), None);
assert_eq!(Decimal::MAX.checked_add(Decimal::MIN), Some(Decimal::ZERO));
assert_eq!(Decimal::MAX.checked_div(Decimal::ZERO), None);
assert_eq!(Decimal::MAX.checked_mul(Decimal::MAX), None);
assert_eq!(Decimal::MAX.checked_mul(Decimal::MIN), None);
assert_eq!(Decimal::MAX.checked_rem(Decimal::ZERO), None);
assert_eq!(Decimal::MAX.checked_sub(Decimal::MAX), Some(Decimal::ZERO));
assert_eq!(Decimal::MAX.checked_sub(Decimal::MIN), None);
assert_eq!(Decimal::MIN.checked_add(Decimal::MAX), Some(Decimal::ZERO));
assert_eq!(Decimal::MIN.checked_add(Decimal::MIN), None);
assert_eq!(Decimal::MIN.checked_div(Decimal::ZERO), None);
assert_eq!(Decimal::MIN.checked_mul(Decimal::MAX), None);
assert_eq!(Decimal::MIN.checked_mul(Decimal::MIN), None);
assert_eq!(Decimal::MIN.checked_rem(Decimal::ZERO), None);
assert_eq!(Decimal::MIN.checked_sub(Decimal::MAX), None);
assert_eq!(Decimal::MIN.checked_sub(Decimal::MIN), Some(Decimal::ZERO));
}
#[test]
fn saturated_methods_have_correct_output() {
assert_eq!(Decimal::MAX.saturating_add(Decimal::MAX), Decimal::MAX);
assert_eq!(Decimal::MAX.saturating_add(Decimal::MIN), Decimal::ZERO);
assert_eq!(Decimal::MAX.saturating_mul(Decimal::MAX), Decimal::MAX);
assert_eq!(Decimal::MAX.saturating_mul(Decimal::MIN), Decimal::MIN);
assert_eq!(Decimal::MAX.saturating_sub(Decimal::MAX), Decimal::ZERO);
assert_eq!(Decimal::MAX.saturating_sub(Decimal::MIN), Decimal::MAX);
assert_eq!(Decimal::MIN.saturating_add(Decimal::MAX), Decimal::ZERO);
assert_eq!(Decimal::MIN.saturating_add(Decimal::MIN), Decimal::MIN);
assert_eq!(Decimal::MIN.saturating_mul(Decimal::MAX), Decimal::MIN);
assert_eq!(Decimal::MIN.saturating_mul(Decimal::MIN), Decimal::MAX);
assert_eq!(Decimal::MIN.saturating_sub(Decimal::MAX), Decimal::MIN);
assert_eq!(Decimal::MIN.saturating_sub(Decimal::MIN), Decimal::ZERO);
}
}