units: Return NumOpResult when implementing Div

Currently we use a std numeric type for the output of various `Div`
implementations while other ops use `NumOpResult`. This makes it
difficult to chain operations.

Throughout the crate use `Output = NumOpResult<Foo>` when implementing
`Div`.

Later we want to enable users differentiating between an overflow and a
div-by-zero. Explicitly do not implement that yet, done separately to
assist review.
This commit is contained in:
Tobin C. Harding 2025-04-07 11:29:12 +10:00
parent dba61c9efe
commit 5fb64953c5
No known key found for this signature in database
GPG Key ID: 40BF9E4C269D6607
4 changed files with 35 additions and 27 deletions

View File

@ -83,9 +83,11 @@ crate::internal_macros::impl_op_for_references! {
fn div(self, rhs: u64) -> Self::Output { self.and_then(|lhs| lhs / rhs) } fn div(self, rhs: u64) -> Self::Output { self.and_then(|lhs| lhs / rhs) }
} }
impl ops::Div<Amount> for Amount { impl ops::Div<Amount> for Amount {
type Output = u64; type Output = NumOpResult<u64>;
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()
}
} }
impl ops::Rem<u64> for Amount { impl ops::Rem<u64> for Amount {
@ -158,9 +160,11 @@ crate::internal_macros::impl_op_for_references! {
fn div(self, rhs: i64) -> Self::Output { self.and_then(|lhs| lhs / rhs) } fn div(self, rhs: i64) -> Self::Output { self.and_then(|lhs| lhs / rhs) }
} }
impl ops::Div<SignedAmount> for SignedAmount { impl ops::Div<SignedAmount> for SignedAmount {
type Output = i64; type Output = NumOpResult<i64>;
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()
}
} }
impl ops::Rem<i64> for SignedAmount { impl ops::Rem<i64> for SignedAmount {

View File

@ -1231,27 +1231,31 @@ fn op_int_combos() {
#[test] #[test]
fn unsigned_amount_div_by_amount() { fn unsigned_amount_div_by_amount() {
assert_eq!(sat(0) / sat(7), 0); assert_eq!((sat(0) / sat(7)).unwrap(), 0);
assert_eq!(sat(1897) / sat(7), 271); assert_eq!((sat(1897) / sat(7)).unwrap(), 271);
} }
#[test] #[test]
#[should_panic(expected = "attempt to divide by zero")] fn unsigned_amount_div_by_amount_zero() {
fn unsigned_amount_div_by_amount_zero() { let _ = sat(1897) / Amount::ZERO; } let res = sat(1897) / Amount::ZERO;
assert!(res.into_result().is_err());
}
#[test] #[test]
fn signed_amount_div_by_amount() { 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)).unwrap(), 271);
assert_eq!(ssat(1897) / ssat(-7), -271); assert_eq!((ssat(1897) / ssat(-7)).unwrap(), -271);
assert_eq!(ssat(-1897) / ssat(7), -271); assert_eq!((ssat(-1897) / ssat(7)).unwrap(), -271);
assert_eq!(ssat(-1897) / ssat(-7), 271); assert_eq!((ssat(-1897) / ssat(-7)).unwrap(), 271);
} }
#[test] #[test]
#[should_panic(expected = "attempt to divide by zero")] fn signed_amount_div_by_amount_zero() {
fn signed_amount_div_by_amount_zero() { let _ = ssat(1897) / SignedAmount::ZERO; } let res = ssat(1897) / SignedAmount::ZERO;
assert!(res.into_result().is_err());
}
#[test] #[test]
fn check_const() { fn check_const() {

View File

@ -178,18 +178,18 @@ crate::internal_macros::impl_op_for_references! {
} }
impl ops::Div<Weight> for Amount { impl ops::Div<Weight> for Amount {
type Output = FeeRate; type Output = NumOpResult<FeeRate>;
fn div(self, rhs: Weight) -> Self::Output { 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()
} }
} }
impl ops::Div<FeeRate> for Amount { impl ops::Div<FeeRate> for Amount {
type Output = 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).unwrap() self.checked_div_by_fee_rate_floor(rhs).valid_or_error()
} }
} }
} }
@ -215,7 +215,7 @@ mod tests {
#[test] #[test]
fn fee_rate_div_by_weight() { 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)); assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(863));
} }
@ -275,21 +275,21 @@ mod tests {
// Test exact division // Test exact division
let amount = Amount::from_sat_u32(1000); let amount = Amount::from_sat_u32(1000);
let fee_rate = FeeRate::from_sat_per_kwu(2); 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)); assert_eq!(weight, Weight::from_wu(500_000));
// Test reference division // 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)); 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)); 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)); assert_eq!(weight_ref3, Weight::from_wu(500_000));
// Test truncation behavior // Test truncation behavior
let amount = Amount::from_sat_u32(1000); let amount = Amount::from_sat_u32(1000);
let fee_rate = FeeRate::from_sat_per_kwu(3); 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 // 1000 * 1000 = 1,000,000 msats
// 1,000,000 / 3 = 333,333.33... wu // 1,000,000 / 3 = 333,333.33... wu
// Should truncate down to 333,333 wu // Should truncate down to 333,333 wu

View File

@ -6,7 +6,7 @@ use core::fmt;
use NumOpResult as R; use NumOpResult as R;
use crate::{Amount, SignedAmount}; use crate::{Amount, FeeRate, SignedAmount, Weight};
/// Result of a mathematical operation on two numeric types. /// Result of a mathematical operation on two numeric types.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
@ -125,7 +125,7 @@ macro_rules! impl_opt_ext {
)* )*
} }
} }
impl_opt_ext!(Amount, SignedAmount); 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)]