units: Enable differentiating div-by-zero

Division by zero is a different error class that overflow. Add a
`MathOp` enum that enables one to check the error class.
This commit is contained in:
Tobin C. Harding 2025-04-07 12:12:20 +10:00
parent 5fb64953c5
commit d6881ff5f8
No known key found for this signature in database
GPG Key ID: 40BF9E4C269D6607
4 changed files with 81 additions and 32 deletions

View File

@ -7,7 +7,7 @@ use core::ops;
use NumOpResult as R; use NumOpResult as R;
use super::{Amount, SignedAmount}; use super::{Amount, SignedAmount};
use crate::{NumOpError, NumOpResult, OptionExt}; use crate::{MathOp, NumOpError, NumOpResult, OptionExt};
impl From<Amount> for NumOpResult<Amount> { impl From<Amount> for NumOpResult<Amount> {
fn from(a: Amount) -> Self { Self::Valid(a) } fn from(a: Amount) -> Self { Self::Valid(a) }
@ -27,7 +27,7 @@ crate::internal_macros::impl_op_for_references! {
impl ops::Add<Amount> for Amount { impl ops::Add<Amount> for Amount {
type Output = NumOpResult<Amount>; type Output = NumOpResult<Amount>;
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<NumOpResult<Amount>> for Amount { impl ops::Add<NumOpResult<Amount>> for Amount {
type Output = NumOpResult<Amount>; type Output = NumOpResult<Amount>;
@ -38,7 +38,7 @@ crate::internal_macros::impl_op_for_references! {
impl ops::Sub<Amount> for Amount { impl ops::Sub<Amount> for Amount {
type Output = NumOpResult<Amount>; type Output = NumOpResult<Amount>;
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<NumOpResult<Amount>> for Amount { impl ops::Sub<NumOpResult<Amount>> for Amount {
type Output = NumOpResult<Amount>; type Output = NumOpResult<Amount>;
@ -54,7 +54,7 @@ crate::internal_macros::impl_op_for_references! {
impl ops::Mul<u64> for Amount { impl ops::Mul<u64> for Amount {
type Output = NumOpResult<Amount>; type Output = NumOpResult<Amount>;
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<u64> for NumOpResult<Amount> { impl ops::Mul<u64> for NumOpResult<Amount> {
type Output = NumOpResult<Amount>; type Output = NumOpResult<Amount>;
@ -64,7 +64,7 @@ crate::internal_macros::impl_op_for_references! {
impl ops::Mul<Amount> for u64 { impl ops::Mul<Amount> for u64 {
type Output = NumOpResult<Amount>; type Output = NumOpResult<Amount>;
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<NumOpResult<Amount>> for u64 { impl ops::Mul<NumOpResult<Amount>> for u64 {
type Output = NumOpResult<Amount>; type Output = NumOpResult<Amount>;
@ -75,7 +75,7 @@ crate::internal_macros::impl_op_for_references! {
impl ops::Div<u64> for Amount { impl ops::Div<u64> for Amount {
type Output = NumOpResult<Amount>; type Output = NumOpResult<Amount>;
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<u64> for NumOpResult<Amount> { impl ops::Div<u64> for NumOpResult<Amount> {
type Output = NumOpResult<Amount>; type Output = NumOpResult<Amount>;
@ -86,14 +86,14 @@ crate::internal_macros::impl_op_for_references! {
type Output = NumOpResult<u64>; type Output = NumOpResult<u64>;
fn div(self, rhs: Amount) -> Self::Output { fn div(self, rhs: Amount) -> Self::Output {
self.to_sat().checked_div(rhs.to_sat()).valid_or_error() self.to_sat().checked_div(rhs.to_sat()).valid_or_error(MathOp::Div)
} }
} }
impl ops::Rem<u64> for Amount { impl ops::Rem<u64> for Amount {
type Output = NumOpResult<Amount>; type Output = NumOpResult<Amount>;
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<u64> for NumOpResult<Amount> { impl ops::Rem<u64> for NumOpResult<Amount> {
type Output = NumOpResult<Amount>; type Output = NumOpResult<Amount>;
@ -104,7 +104,7 @@ crate::internal_macros::impl_op_for_references! {
impl ops::Add<SignedAmount> for SignedAmount { impl ops::Add<SignedAmount> for SignedAmount {
type Output = NumOpResult<SignedAmount>; type Output = NumOpResult<SignedAmount>;
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<NumOpResult<SignedAmount>> for SignedAmount { impl ops::Add<NumOpResult<SignedAmount>> for SignedAmount {
type Output = NumOpResult<SignedAmount>; type Output = NumOpResult<SignedAmount>;
@ -115,7 +115,7 @@ crate::internal_macros::impl_op_for_references! {
impl ops::Sub<SignedAmount> for SignedAmount { impl ops::Sub<SignedAmount> for SignedAmount {
type Output = NumOpResult<SignedAmount>; type Output = NumOpResult<SignedAmount>;
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<NumOpResult<SignedAmount>> for SignedAmount { impl ops::Sub<NumOpResult<SignedAmount>> for SignedAmount {
type Output = NumOpResult<SignedAmount>; type Output = NumOpResult<SignedAmount>;
@ -131,7 +131,7 @@ crate::internal_macros::impl_op_for_references! {
impl ops::Mul<i64> for SignedAmount { impl ops::Mul<i64> for SignedAmount {
type Output = NumOpResult<SignedAmount>; type Output = NumOpResult<SignedAmount>;
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<i64> for NumOpResult<SignedAmount> { impl ops::Mul<i64> for NumOpResult<SignedAmount> {
type Output = NumOpResult<SignedAmount>; type Output = NumOpResult<SignedAmount>;
@ -141,7 +141,7 @@ crate::internal_macros::impl_op_for_references! {
impl ops::Mul<SignedAmount> for i64 { impl ops::Mul<SignedAmount> for i64 {
type Output = NumOpResult<SignedAmount>; type Output = NumOpResult<SignedAmount>;
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<NumOpResult<SignedAmount>> for i64 { impl ops::Mul<NumOpResult<SignedAmount>> for i64 {
type Output = NumOpResult<SignedAmount>; type Output = NumOpResult<SignedAmount>;
@ -152,7 +152,7 @@ crate::internal_macros::impl_op_for_references! {
impl ops::Div<i64> for SignedAmount { impl ops::Div<i64> for SignedAmount {
type Output = NumOpResult<SignedAmount>; type Output = NumOpResult<SignedAmount>;
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<i64> for NumOpResult<SignedAmount> { impl ops::Div<i64> for NumOpResult<SignedAmount> {
type Output = NumOpResult<SignedAmount>; type Output = NumOpResult<SignedAmount>;
@ -163,14 +163,14 @@ crate::internal_macros::impl_op_for_references! {
type Output = NumOpResult<i64>; type Output = NumOpResult<i64>;
fn div(self, rhs: SignedAmount) -> Self::Output { fn div(self, rhs: SignedAmount) -> Self::Output {
self.to_sat().checked_div(rhs.to_sat()).valid_or_error() self.to_sat().checked_div(rhs.to_sat()).valid_or_error(MathOp::Div)
} }
} }
impl ops::Rem<i64> for SignedAmount { impl ops::Rem<i64> for SignedAmount {
type Output = NumOpResult<SignedAmount>; type Output = NumOpResult<SignedAmount>;
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<i64> for NumOpResult<SignedAmount> { impl ops::Rem<i64> for NumOpResult<SignedAmount> {
type Output = NumOpResult<SignedAmount>; type Output = NumOpResult<SignedAmount>;
@ -187,7 +187,7 @@ crate::internal_macros::impl_op_for_references! {
fn add(self, rhs: Self) -> Self::Output { fn add(self, rhs: Self) -> Self::Output {
match (self, rhs) { match (self, rhs) {
(R::Valid(lhs), R::Valid(rhs)) => lhs + rhs, (R::Valid(lhs), R::Valid(rhs)) => lhs + rhs,
(_, _) => R::Error(NumOpError {}), (_, _) => R::Error(NumOpError::while_doing(MathOp::Add)),
} }
} }
} }
@ -210,7 +210,7 @@ crate::internal_macros::impl_op_for_references! {
fn sub(self, rhs: Self) -> Self::Output { fn sub(self, rhs: Self) -> Self::Output {
match (self, rhs) { match (self, rhs) {
(R::Valid(lhs), R::Valid(rhs)) => lhs - rhs, (R::Valid(lhs), R::Valid(rhs)) => lhs - rhs,
(_, _) => R::Error(NumOpError {}), (_, _) => R::Error(NumOpError::while_doing(MathOp::Sub)),
} }
} }
} }
@ -245,7 +245,7 @@ impl core::iter::Sum<NumOpResult<Amount>> for NumOpResult<Amount> {
{ {
iter.fold(R::Valid(Amount::ZERO), |acc, amount| match (acc, amount) { iter.fold(R::Valid(Amount::ZERO), |acc, amount| match (acc, amount) {
(R::Valid(lhs), R::Valid(rhs)) => lhs + rhs, (R::Valid(lhs), R::Valid(rhs)) => lhs + rhs,
(_, _) => R::Error(NumOpError {}), (_, _) => R::Error(NumOpError::while_doing(MathOp::Add)),
}) })
} }
} }
@ -256,7 +256,7 @@ impl<'a> core::iter::Sum<&'a NumOpResult<Amount>> for NumOpResult<Amount> {
{ {
iter.fold(R::Valid(Amount::ZERO), |acc, amount| match (acc, amount) { iter.fold(R::Valid(Amount::ZERO), |acc, amount| match (acc, amount) {
(R::Valid(lhs), R::Valid(rhs)) => lhs + rhs, (R::Valid(lhs), R::Valid(rhs)) => lhs + rhs,
(_, _) => R::Error(NumOpError {}), (_, _) => R::Error(NumOpError::while_doing(MathOp::Add)),
}) })
} }
} }
@ -268,7 +268,7 @@ impl core::iter::Sum<NumOpResult<SignedAmount>> for NumOpResult<SignedAmount> {
{ {
iter.fold(R::Valid(SignedAmount::ZERO), |acc, amount| match (acc, amount) { iter.fold(R::Valid(SignedAmount::ZERO), |acc, amount| match (acc, amount) {
(R::Valid(lhs), R::Valid(rhs)) => lhs + rhs, (R::Valid(lhs), R::Valid(rhs)) => lhs + rhs,
(_, _) => R::Error(NumOpError {}), (_, _) => R::Error(NumOpError::while_doing(MathOp::Add)),
}) })
} }
} }
@ -279,7 +279,7 @@ impl<'a> core::iter::Sum<&'a NumOpResult<SignedAmount>> for NumOpResult<SignedAm
{ {
iter.fold(R::Valid(SignedAmount::ZERO), |acc, amount| match (acc, amount) { iter.fold(R::Valid(SignedAmount::ZERO), |acc, amount| match (acc, amount) {
(R::Valid(lhs), R::Valid(rhs)) => lhs + rhs, (R::Valid(lhs), R::Valid(rhs)) => lhs + rhs,
(_, _) => R::Error(NumOpError {}), (_, _) => R::Error(NumOpError::while_doing(MathOp::Add)),
}) })
} }
} }

View File

@ -13,7 +13,7 @@
use core::ops; use core::ops;
use crate::{Amount, FeeRate, NumOpResult, OptionExt, Weight}; use crate::{Amount, FeeRate, MathOp, NumOpResult, OptionExt, Weight};
impl Amount { impl Amount {
/// Checked weight ceiling division. /// Checked weight ceiling division.
@ -166,14 +166,14 @@ crate::internal_macros::impl_op_for_references! {
impl ops::Mul<FeeRate> for Weight { impl ops::Mul<FeeRate> for Weight {
type Output = NumOpResult<Amount>; type Output = NumOpResult<Amount>;
fn mul(self, rhs: FeeRate) -> Self::Output { 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<Weight> for FeeRate { impl ops::Mul<Weight> for FeeRate {
type Output = NumOpResult<Amount>; type Output = NumOpResult<Amount>;
fn mul(self, rhs: Weight) -> Self::Output { 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)
} }
} }
@ -181,7 +181,7 @@ crate::internal_macros::impl_op_for_references! {
type Output = NumOpResult<FeeRate>; type Output = NumOpResult<FeeRate>;
fn div(self, rhs: Weight) -> Self::Output { fn div(self, rhs: Weight) -> Self::Output {
self.checked_div_by_weight_floor(rhs).valid_or_error() self.checked_div_by_weight_floor(rhs).valid_or_error(MathOp::Div)
} }
} }
@ -189,7 +189,7 @@ crate::internal_macros::impl_op_for_references! {
type Output = NumOpResult<Weight>; type Output = NumOpResult<Weight>;
fn div(self, rhs: FeeRate) -> Self::Output { fn div(self, rhs: FeeRate) -> Self::Output {
self.checked_div_by_fee_rate_floor(rhs).valid_or_error() self.checked_div_by_fee_rate_floor(rhs).valid_or_error(MathOp::Div)
} }
} }
} }

View File

@ -44,7 +44,7 @@ pub use self::{
amount::{Amount, SignedAmount}, amount::{Amount, SignedAmount},
block::{BlockHeight, BlockInterval}, block::{BlockHeight, BlockInterval},
fee_rate::FeeRate, fee_rate::FeeRate,
result::{NumOpError, NumOpResult}, result::{NumOpError, NumOpResult, MathOp},
time::BlockTime, time::BlockTime,
weight::Weight weight::Weight
}; };

View File

@ -107,7 +107,7 @@ impl<T: fmt::Debug> NumOpResult<T> {
} }
pub(crate) trait OptionExt<T> { pub(crate) trait OptionExt<T> {
fn valid_or_error(self) -> NumOpResult<T>; fn valid_or_error(self, op: MathOp) -> NumOpResult<T>;
} }
macro_rules! impl_opt_ext { macro_rules! impl_opt_ext {
@ -115,10 +115,10 @@ macro_rules! impl_opt_ext {
$( $(
impl OptionExt<$ty> for Option<$ty> { impl OptionExt<$ty> for Option<$ty> {
#[inline] #[inline]
fn valid_or_error(self) -> NumOpResult<$ty> { fn valid_or_error(self, op: MathOp) -> NumOpResult<$ty> {
match self { match self {
Some(amount) => R::Valid(amount), Some(amount) => R::Valid(amount),
None => R::Error(NumOpError {}), None => R::Error(NumOpError(op)),
} }
} }
} }
@ -130,13 +130,62 @@ impl_opt_ext!(Amount, SignedAmount, u64, i64, FeeRate, Weight);
/// An error occurred while doing a mathematical operation. /// An error occurred while doing a mathematical operation.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[non_exhaustive] #[non_exhaustive]
pub struct NumOpError; 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 { impl fmt::Display for NumOpError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "a math operation gave an invalid numeric result") write!(f, "math operation '{}' gave an invalid numeric result", self.operation())
} }
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
impl std::error::Error for NumOpError {} 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"),
}
}
}