diff --git a/units/src/amount/mod.rs b/units/src/amount/mod.rs index 8928cea9d..6fb09fbcf 100644 --- a/units/src/amount/mod.rs +++ b/units/src/amount/mod.rs @@ -35,11 +35,9 @@ pub use self::{ OutOfRangeError, ParseAmountError, ParseDenominationError, ParseError, PossiblyConfusingDenominationError, TooPreciseError, UnknownDenominationError, }, - result::{NumOpError, NumOpResult}, signed::SignedAmount, unsigned::Amount, }; -pub(crate) use self::result::OptionExt; /// A set of denominations in which amounts can be expressed. /// diff --git a/units/src/amount/result.rs b/units/src/amount/result.rs index ab06f207f..614e55bd8 100644 --- a/units/src/amount/result.rs +++ b/units/src/amount/result.rs @@ -1,112 +1,13 @@ // SPDX-License-Identifier: CC0-1.0 -//! Provides a monodic type used when mathematical operations (`core::ops`) return an amount type. +//! Provides a monodic type returned by mathematical operations (`core::ops`). -use core::{fmt, ops}; +use core::ops; use NumOpResult as R; use super::{Amount, SignedAmount}; - -/// Result of an operation on [`Amount`] or [`SignedAmount`]. -/// -/// The type parameter `T` should be normally `Amount` or `SignedAmount`. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[must_use] -pub enum NumOpResult { - /// Result of a successful mathematical operation. - Valid(T), - /// Result of an unsuccessful mathematical operation. - Error(NumOpError), -} - -impl NumOpResult { - /// Returns the contained valid amount, consuming `self`. - /// - /// # Panics - /// - /// Panics with `msg` if the numeric result is an `Error`. - #[inline] - #[track_caller] - pub fn expect(self, msg: &str) -> T { - match self { - R::Valid(amount) => amount, - R::Error(_) => panic!("{}", msg), - } - } - - /// Returns the contained valid amount, consuming `self`. - /// - /// # Panics - /// - /// Panics if the numeric result is an `Error`. - #[inline] - #[track_caller] - pub fn unwrap(self) -> T { - match self { - R::Valid(amount) => amount, - R::Error(e) => panic!("tried to unwrap an invalid numeric result: {:?}", e), - } - } - - /// Returns the contained error, consuming `self`. - /// - /// # Panics - /// - /// Panics if the numeric result is a valid amount. - #[inline] - #[track_caller] - pub fn unwrap_err(self) -> NumOpError { - match self { - R::Error(e) => e, - R::Valid(a) => panic!("tried to unwrap a valid numeric result: {:?}", a), - } - } - - /// Converts this `NumOpResult` to an `Option`. - #[inline] - pub fn ok(self) -> Option { - match self { - R::Valid(amount) => Some(amount), - R::Error(_) => None, - } - } - - /// Converts this `NumOpResult` to a `Result`. - #[inline] - #[allow(clippy::missing_errors_doc)] - pub fn into_result(self) -> Result { - match self { - R::Valid(amount) => Ok(amount), - R::Error(e) => Err(e), - } - } - - /// Calls `op` if the numeric result is `Valid`, otherwise returns the `Error` value of `self`. - #[inline] - pub fn and_then(self, op: F) -> NumOpResult - where - F: FnOnce(T) -> NumOpResult, - { - match self { - R::Valid(amount) => op(amount), - R::Error(e) => R::Error(e), - } - } - - /// Returns `true` if the numeric result is a valid amount. - #[inline] - pub fn is_valid(&self) -> bool { - match self { - R::Valid(_) => true, - R::Error(_) => false, - } - } - - /// Returns `true` if the numeric result is an invalid amount. - #[inline] - pub fn is_error(&self) -> bool { !self.is_valid() } -} +use crate::{MathOp, NumOpError, NumOpResult, OptionExt}; impl From for NumOpResult { fn from(a: Amount) -> Self { Self::Valid(a) } @@ -126,7 +27,7 @@ crate::internal_macros::impl_op_for_references! { impl ops::Add for Amount { type Output = NumOpResult; - fn add(self, rhs: Amount) -> Self::Output { self.checked_add(rhs).valid_or_error() } + fn add(self, rhs: Amount) -> Self::Output { self.checked_add(rhs).valid_or_error(MathOp::Add) } } impl ops::Add> for Amount { type Output = NumOpResult; @@ -137,7 +38,7 @@ crate::internal_macros::impl_op_for_references! { impl ops::Sub for Amount { type Output = NumOpResult; - fn sub(self, rhs: Amount) -> Self::Output { self.checked_sub(rhs).valid_or_error() } + fn sub(self, rhs: Amount) -> Self::Output { self.checked_sub(rhs).valid_or_error(MathOp::Sub) } } impl ops::Sub> for Amount { type Output = NumOpResult; @@ -153,7 +54,7 @@ crate::internal_macros::impl_op_for_references! { impl ops::Mul for Amount { type Output = NumOpResult; - fn mul(self, rhs: u64) -> Self::Output { self.checked_mul(rhs).valid_or_error() } + fn mul(self, rhs: u64) -> Self::Output { self.checked_mul(rhs).valid_or_error(MathOp::Mul) } } impl ops::Mul for NumOpResult { type Output = NumOpResult; @@ -163,7 +64,7 @@ crate::internal_macros::impl_op_for_references! { impl ops::Mul for u64 { type Output = NumOpResult; - fn mul(self, rhs: Amount) -> Self::Output { rhs.checked_mul(self).valid_or_error() } + fn mul(self, rhs: Amount) -> Self::Output { rhs.checked_mul(self).valid_or_error(MathOp::Mul) } } impl ops::Mul> for u64 { type Output = NumOpResult; @@ -174,7 +75,7 @@ crate::internal_macros::impl_op_for_references! { impl ops::Div for Amount { type Output = NumOpResult; - fn div(self, rhs: u64) -> Self::Output { self.checked_div(rhs).valid_or_error() } + fn div(self, rhs: u64) -> Self::Output { self.checked_div(rhs).valid_or_error(MathOp::Div) } } impl ops::Div for NumOpResult { type Output = NumOpResult; @@ -182,15 +83,17 @@ crate::internal_macros::impl_op_for_references! { fn div(self, rhs: u64) -> Self::Output { self.and_then(|lhs| lhs / rhs) } } impl ops::Div for Amount { - type Output = u64; + type Output = NumOpResult; - fn div(self, rhs: Amount) -> Self::Output { self.to_sat() / rhs.to_sat() } + fn div(self, rhs: Amount) -> Self::Output { + self.to_sat().checked_div(rhs.to_sat()).valid_or_error(MathOp::Div) + } } impl ops::Rem for Amount { type Output = NumOpResult; - fn rem(self, modulus: u64) -> Self::Output { self.checked_rem(modulus).valid_or_error() } + fn rem(self, modulus: u64) -> Self::Output { self.checked_rem(modulus).valid_or_error(MathOp::Rem) } } impl ops::Rem for NumOpResult { type Output = NumOpResult; @@ -201,7 +104,7 @@ crate::internal_macros::impl_op_for_references! { impl ops::Add for SignedAmount { type Output = NumOpResult; - fn add(self, rhs: SignedAmount) -> Self::Output { self.checked_add(rhs).valid_or_error() } + fn add(self, rhs: SignedAmount) -> Self::Output { self.checked_add(rhs).valid_or_error(MathOp::Add) } } impl ops::Add> for SignedAmount { type Output = NumOpResult; @@ -212,7 +115,7 @@ crate::internal_macros::impl_op_for_references! { impl ops::Sub for SignedAmount { type Output = NumOpResult; - fn sub(self, rhs: SignedAmount) -> Self::Output { self.checked_sub(rhs).valid_or_error() } + fn sub(self, rhs: SignedAmount) -> Self::Output { self.checked_sub(rhs).valid_or_error(MathOp::Sub) } } impl ops::Sub> for SignedAmount { type Output = NumOpResult; @@ -228,7 +131,7 @@ crate::internal_macros::impl_op_for_references! { impl ops::Mul for SignedAmount { type Output = NumOpResult; - fn mul(self, rhs: i64) -> Self::Output { self.checked_mul(rhs).valid_or_error() } + fn mul(self, rhs: i64) -> Self::Output { self.checked_mul(rhs).valid_or_error(MathOp::Mul) } } impl ops::Mul for NumOpResult { type Output = NumOpResult; @@ -238,7 +141,7 @@ crate::internal_macros::impl_op_for_references! { impl ops::Mul for i64 { type Output = NumOpResult; - fn mul(self, rhs: SignedAmount) -> Self::Output { rhs.checked_mul(self).valid_or_error() } + fn mul(self, rhs: SignedAmount) -> Self::Output { rhs.checked_mul(self).valid_or_error(MathOp::Mul) } } impl ops::Mul> for i64 { type Output = NumOpResult; @@ -249,7 +152,7 @@ crate::internal_macros::impl_op_for_references! { impl ops::Div for SignedAmount { type Output = NumOpResult; - fn div(self, rhs: i64) -> Self::Output { self.checked_div(rhs).valid_or_error() } + fn div(self, rhs: i64) -> Self::Output { self.checked_div(rhs).valid_or_error(MathOp::Div) } } impl ops::Div for NumOpResult { type Output = NumOpResult; @@ -257,15 +160,17 @@ crate::internal_macros::impl_op_for_references! { fn div(self, rhs: i64) -> Self::Output { self.and_then(|lhs| lhs / rhs) } } impl ops::Div for SignedAmount { - type Output = i64; + type Output = NumOpResult; - fn div(self, rhs: SignedAmount) -> Self::Output { self.to_sat() / rhs.to_sat() } + fn div(self, rhs: SignedAmount) -> Self::Output { + self.to_sat().checked_div(rhs.to_sat()).valid_or_error(MathOp::Div) + } } impl ops::Rem for SignedAmount { type Output = NumOpResult; - fn rem(self, modulus: i64) -> Self::Output { self.checked_rem(modulus).valid_or_error() } + fn rem(self, modulus: i64) -> Self::Output { self.checked_rem(modulus).valid_or_error(MathOp::Rem) } } impl ops::Rem for NumOpResult { type Output = NumOpResult; @@ -282,7 +187,7 @@ crate::internal_macros::impl_op_for_references! { fn add(self, rhs: Self) -> Self::Output { match (self, rhs) { (R::Valid(lhs), R::Valid(rhs)) => lhs + rhs, - (_, _) => R::Error(NumOpError {}), + (_, _) => R::Error(NumOpError::while_doing(MathOp::Add)), } } } @@ -305,7 +210,7 @@ crate::internal_macros::impl_op_for_references! { fn sub(self, rhs: Self) -> Self::Output { match (self, rhs) { (R::Valid(lhs), R::Valid(rhs)) => lhs - rhs, - (_, _) => R::Error(NumOpError {}), + (_, _) => R::Error(NumOpError::while_doing(MathOp::Sub)), } } } @@ -340,7 +245,7 @@ impl core::iter::Sum> for NumOpResult { { iter.fold(R::Valid(Amount::ZERO), |acc, amount| match (acc, amount) { (R::Valid(lhs), R::Valid(rhs)) => lhs + rhs, - (_, _) => R::Error(NumOpError {}), + (_, _) => R::Error(NumOpError::while_doing(MathOp::Add)), }) } } @@ -351,7 +256,7 @@ impl<'a> core::iter::Sum<&'a NumOpResult> for NumOpResult { { iter.fold(R::Valid(Amount::ZERO), |acc, amount| match (acc, amount) { (R::Valid(lhs), R::Valid(rhs)) => lhs + rhs, - (_, _) => R::Error(NumOpError {}), + (_, _) => R::Error(NumOpError::while_doing(MathOp::Add)), }) } } @@ -363,7 +268,7 @@ impl core::iter::Sum> for NumOpResult { { iter.fold(R::Valid(SignedAmount::ZERO), |acc, amount| match (acc, amount) { (R::Valid(lhs), R::Valid(rhs)) => lhs + rhs, - (_, _) => R::Error(NumOpError {}), + (_, _) => R::Error(NumOpError::while_doing(MathOp::Add)), }) } } @@ -374,45 +279,7 @@ impl<'a> core::iter::Sum<&'a NumOpResult> for NumOpResult lhs + rhs, - (_, _) => R::Error(NumOpError {}), + (_, _) => R::Error(NumOpError::while_doing(MathOp::Add)), }) } } - -pub(crate) trait OptionExt { - fn valid_or_error(self) -> NumOpResult; -} - -impl OptionExt for Option { - #[inline] - fn valid_or_error(self) -> NumOpResult { - match self { - Some(amount) => R::Valid(amount), - None => R::Error(NumOpError {}), - } - } -} - -impl OptionExt for Option { - #[inline] - fn valid_or_error(self) -> NumOpResult { - match self { - Some(amount) => R::Valid(amount), - None => R::Error(NumOpError {}), - } - } -} - -/// An error occurred while doing a mathematical operation. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[non_exhaustive] -pub struct NumOpError; - -impl fmt::Display for NumOpError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "a math operation on amounts gave an invalid numeric result") - } -} - -#[cfg(feature = "std")] -impl std::error::Error for NumOpError {} diff --git a/units/src/amount/tests.rs b/units/src/amount/tests.rs index f8aba133f..a2a3bec07 100644 --- a/units/src/amount/tests.rs +++ b/units/src/amount/tests.rs @@ -13,6 +13,7 @@ use std::panic; use ::serde::{Deserialize, Serialize}; use super::*; +use crate::NumOpResult; #[cfg(feature = "alloc")] use crate::{FeeRate, Weight}; @@ -1230,27 +1231,31 @@ fn op_int_combos() { #[test] fn unsigned_amount_div_by_amount() { - assert_eq!(sat(0) / sat(7), 0); - assert_eq!(sat(1897) / sat(7), 271); + assert_eq!((sat(0) / sat(7)).unwrap(), 0); + assert_eq!((sat(1897) / sat(7)).unwrap(), 271); } #[test] -#[should_panic(expected = "attempt to divide by zero")] -fn unsigned_amount_div_by_amount_zero() { let _ = sat(1897) / Amount::ZERO; } +fn unsigned_amount_div_by_amount_zero() { + let res = sat(1897) / Amount::ZERO; + assert!(res.into_result().is_err()); +} #[test] fn signed_amount_div_by_amount() { - assert_eq!(ssat(0) / ssat(7), 0); + assert_eq!((ssat(0) / ssat(7)).unwrap(), 0); - assert_eq!(ssat(1897) / ssat(7), 271); - assert_eq!(ssat(1897) / ssat(-7), -271); - assert_eq!(ssat(-1897) / ssat(7), -271); - assert_eq!(ssat(-1897) / ssat(-7), 271); + assert_eq!((ssat(1897) / ssat(7)).unwrap(), 271); + assert_eq!((ssat(1897) / ssat(-7)).unwrap(), -271); + assert_eq!((ssat(-1897) / ssat(7)).unwrap(), -271); + assert_eq!((ssat(-1897) / ssat(-7)).unwrap(), 271); } #[test] -#[should_panic(expected = "attempt to divide by zero")] -fn signed_amount_div_by_amount_zero() { let _ = ssat(1897) / SignedAmount::ZERO; } +fn signed_amount_div_by_amount_zero() { + let res = ssat(1897) / SignedAmount::ZERO; + assert!(res.into_result().is_err()); +} #[test] fn check_const() { diff --git a/units/src/fee.rs b/units/src/fee.rs index 21d223c9c..45d18e8dd 100644 --- a/units/src/fee.rs +++ b/units/src/fee.rs @@ -13,8 +13,7 @@ use core::ops; -use crate::amount::{NumOpResult, OptionExt}; -use crate::{Amount, FeeRate, Weight}; +use crate::{Amount, FeeRate, MathOp, NumOpResult, OptionExt, Weight}; impl Amount { /// Checked weight ceiling division. @@ -167,30 +166,30 @@ crate::internal_macros::impl_op_for_references! { impl ops::Mul for Weight { type Output = NumOpResult; fn mul(self, rhs: FeeRate) -> Self::Output { - rhs.checked_mul_by_weight(self).valid_or_error() + rhs.checked_mul_by_weight(self).valid_or_error(MathOp::Mul) } } impl ops::Mul for FeeRate { type Output = NumOpResult; fn mul(self, rhs: Weight) -> Self::Output { - self.checked_mul_by_weight(rhs).valid_or_error() + self.checked_mul_by_weight(rhs).valid_or_error(MathOp::Mul) } } impl ops::Div for Amount { - type Output = FeeRate; + type Output = NumOpResult; fn div(self, rhs: Weight) -> Self::Output { - FeeRate::from_sat_per_kwu(self.to_sat() * 1000 / rhs.to_wu()) + self.checked_div_by_weight_floor(rhs).valid_or_error(MathOp::Div) } } impl ops::Div for Amount { - type Output = Weight; + type Output = NumOpResult; fn div(self, rhs: FeeRate) -> Self::Output { - self.checked_div_by_fee_rate_floor(rhs).unwrap() + self.checked_div_by_fee_rate_floor(rhs).valid_or_error(MathOp::Div) } } } @@ -216,7 +215,7 @@ mod tests { #[test] fn fee_rate_div_by_weight() { - let fee_rate = Amount::from_sat_u32(329) / Weight::from_wu(381); + let fee_rate = (Amount::from_sat_u32(329) / Weight::from_wu(381)).unwrap(); assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(863)); } @@ -276,21 +275,21 @@ mod tests { // Test exact division let amount = Amount::from_sat_u32(1000); let fee_rate = FeeRate::from_sat_per_kwu(2); - let weight = amount / fee_rate; + let weight = (amount / fee_rate).unwrap(); assert_eq!(weight, Weight::from_wu(500_000)); // Test reference division - let weight_ref = &amount / fee_rate; + let weight_ref = (&amount / fee_rate).unwrap(); assert_eq!(weight_ref, Weight::from_wu(500_000)); - let weight_ref2 = amount / &fee_rate; + let weight_ref2 = (amount / &fee_rate).unwrap(); assert_eq!(weight_ref2, Weight::from_wu(500_000)); - let weight_ref3 = &amount / &fee_rate; + let weight_ref3 = (&amount / &fee_rate).unwrap(); assert_eq!(weight_ref3, Weight::from_wu(500_000)); // Test truncation behavior let amount = Amount::from_sat_u32(1000); let fee_rate = FeeRate::from_sat_per_kwu(3); - let weight = amount / fee_rate; + let weight = (amount / fee_rate).unwrap(); // 1000 * 1000 = 1,000,000 msats // 1,000,000 / 3 = 333,333.33... wu // Should truncate down to 333,333 wu diff --git a/units/src/internal_macros.rs b/units/src/internal_macros.rs index c5f0ac6fc..ee6fc33d6 100644 --- a/units/src/internal_macros.rs +++ b/units/src/internal_macros.rs @@ -4,7 +4,7 @@ //! //! Macros meant to be used inside the `bitcoin-units` library. -/// Implements an opcode for various reference combinations. +/// Implements a mathematical operation for various reference combinations. /// /// Given `$ty`, assumes the `$op_trait<$other_ty>` trait is implemented on it, /// and implements the same trait with the full matrix of `&$ty` and `&$other_ty`: diff --git a/units/src/lib.rs b/units/src/lib.rs index 4a166ebb2..0919efb73 100644 --- a/units/src/lib.rs +++ b/units/src/lib.rs @@ -20,6 +20,7 @@ extern crate std; mod fee; mod internal_macros; +mod result; #[doc(hidden)] pub mod _export { @@ -43,6 +44,8 @@ pub use self::{ amount::{Amount, SignedAmount}, block::{BlockHeight, BlockInterval}, fee_rate::FeeRate, + result::{NumOpError, NumOpResult, MathOp}, time::BlockTime, weight::Weight }; +pub(crate) use self::result::OptionExt; diff --git a/units/src/result.rs b/units/src/result.rs new file mode 100644 index 000000000..8276e6cc1 --- /dev/null +++ b/units/src/result.rs @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Provides a monodic type returned by mathematical operations (`core::ops`). + +use core::fmt; + +use NumOpResult as R; + +use crate::{Amount, FeeRate, SignedAmount, Weight}; + +/// Result of a mathematical operation on two numeric types. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[must_use] +pub enum NumOpResult { + /// Result of a successful mathematical operation. + Valid(T), + /// Result of an unsuccessful mathematical operation. + Error(NumOpError), +} + +impl NumOpResult { + /// Returns the contained valid numeric type, consuming `self`. + /// + /// # Panics + /// + /// Panics with `msg` if the numeric result is an `Error`. + #[inline] + #[track_caller] + pub fn expect(self, msg: &str) -> T { + match self { + R::Valid(x) => x, + R::Error(_) => panic!("{}", msg), + } + } + + /// Returns the contained valid numeric type, consuming `self`. + /// + /// # Panics + /// + /// Panics if the numeric result is an `Error`. + #[inline] + #[track_caller] + pub fn unwrap(self) -> T { + match self { + R::Valid(x) => x, + R::Error(e) => panic!("tried to unwrap an invalid numeric result: {:?}", e), + } + } + + /// Returns the contained error, consuming `self`. + /// + /// # Panics + /// + /// Panics if the numeric result is valid. + #[inline] + #[track_caller] + pub fn unwrap_err(self) -> NumOpError { + match self { + R::Error(e) => e, + R::Valid(a) => panic!("tried to unwrap a valid numeric result: {:?}", a), + } + } + + /// Converts this `NumOpResult` to an `Option`. + #[inline] + pub fn ok(self) -> Option { + match self { + R::Valid(x) => Some(x), + R::Error(_) => None, + } + } + + /// Converts this `NumOpResult` to a `Result`. + #[inline] + #[allow(clippy::missing_errors_doc)] + pub fn into_result(self) -> Result { + match self { + R::Valid(x) => Ok(x), + R::Error(e) => Err(e), + } + } + + /// Calls `op` if the numeric result is `Valid`, otherwise returns the `Error` value of `self`. + #[inline] + pub fn and_then(self, op: F) -> NumOpResult + where + F: FnOnce(T) -> NumOpResult, + { + match self { + R::Valid(x) => op(x), + R::Error(e) => R::Error(e), + } + } + + /// Returns `true` if the numeric result is valid. + #[inline] + pub fn is_valid(&self) -> bool { + match self { + R::Valid(_) => true, + R::Error(_) => false, + } + } + + /// Returns `true` if the numeric result is invalid. + #[inline] + pub fn is_error(&self) -> bool { !self.is_valid() } +} + +pub(crate) trait OptionExt { + fn valid_or_error(self, op: MathOp) -> NumOpResult; +} + +macro_rules! impl_opt_ext { + ($($ty:ident),* $(,)?) => { + $( + impl OptionExt<$ty> for Option<$ty> { + #[inline] + fn valid_or_error(self, op: MathOp) -> NumOpResult<$ty> { + match self { + Some(amount) => R::Valid(amount), + None => R::Error(NumOpError(op)), + } + } + } + )* + } +} +impl_opt_ext!(Amount, SignedAmount, u64, i64, FeeRate, Weight); + +/// An error occurred while doing a mathematical operation. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct NumOpError(MathOp); + +impl NumOpError { + /// Creates a [`NumOpError`] caused by `op`. + pub fn while_doing(op: MathOp) -> Self { NumOpError(op) } + + /// Returns the [`MathOp`] that caused this error. + pub fn operation(self) -> MathOp { self.0 } +} + +impl fmt::Display for NumOpError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "math operation '{}' gave an invalid numeric result", self.operation()) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for NumOpError {} + +/// The math operation that caused the error. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum MathOp { + /// Addition failed ([`core::ops::Add`] resulted in an invalid value). + Add, + /// Subtraction failed ([`core::ops::Sub`] resulted in an invalid value). + Sub, + /// Multiplication failed ([`core::ops::Mul`] resulted in an invalid value). + Mul, + /// Division failed ([`core::ops::Div`] attempted div-by-zero). + Div, + /// Calculating the remainder failed ([`core::ops::Rem`] attempted div-by-zero). + Rem, + /// Negation failed ([`core::ops::Neg`] resulted in an invalid value). + Neg, +} + +impl MathOp { + /// Returns `true` if this operation error'ed due to overflow. + pub fn is_overflow(self) -> bool { + matches!(self, MathOp::Add | MathOp::Sub | MathOp::Mul | MathOp::Neg) + } + + /// Returns `true` if this operation error'ed due to division by zero. + pub fn is_div_by_zero(self) -> bool { !self.is_overflow() } +} + +impl fmt::Display for MathOp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + MathOp::Add => write!(f, "add"), + MathOp::Sub => write!(f, "sub"), + MathOp::Mul => write!(f, "mul"), + MathOp::Div => write!(f, "div"), + MathOp::Rem => write!(f, "rem"), + MathOp::Neg => write!(f, "neg"), + } + } +}