Merge rust-bitcoin/rust-bitcoin#4312: units: Return `NumOpResult` when implementing `Div`
d6881ff5f8
units: Enable differentiating div-by-zero (Tobin C. Harding)5fb64953c5
units: Return NumOpResult when implementing Div (Tobin C. Harding)dba61c9efe
units: Fix internal docs (Tobin C. Harding)512326b8b9
units: Macroize implementing OptionExt (Tobin C. Harding)f5b54e5fe0
units: Move general result stuff to a separate module (Tobin C. Harding)f49efdf3f7
units: Remove mention of amount in result module (Tobin C. Harding) Pull request description: First 4 patches are preparatory clean up. Patch 5 and 6 make all implementations of `Div` in the crate return `NumOpResult`. Done as two separate patches to assist review and in case we need to iterate, patch 5 is likely trivial to review. Fix: #4215 ACKs for top commit: apoelstra: ACK d6881ff5f861b8a85883ec88ce453cc02191ba14; successfully ran local tests Tree-SHA512: 7188262dbe61d5b7ec051b4310918680b2732788f364419d85e30a98c94778cffabda166ef716fe2298a70b69626aaa3b51cff0ce6efeeb3e93e51066ab358a1
This commit is contained in:
commit
d222fdd879
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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<T> {
|
||||
/// Result of a successful mathematical operation.
|
||||
Valid(T),
|
||||
/// Result of an unsuccessful mathematical operation.
|
||||
Error(NumOpError),
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> NumOpResult<T> {
|
||||
/// 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<T>`.
|
||||
#[inline]
|
||||
pub fn ok(self) -> Option<T> {
|
||||
match self {
|
||||
R::Valid(amount) => Some(amount),
|
||||
R::Error(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts this `NumOpResult` to a `Result<T, NumOpError>`.
|
||||
#[inline]
|
||||
#[allow(clippy::missing_errors_doc)]
|
||||
pub fn into_result(self) -> Result<T, NumOpError> {
|
||||
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<F>(self, op: F) -> NumOpResult<T>
|
||||
where
|
||||
F: FnOnce(T) -> NumOpResult<T>,
|
||||
{
|
||||
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<Amount> for NumOpResult<Amount> {
|
||||
fn from(a: Amount) -> Self { Self::Valid(a) }
|
||||
|
@ -126,7 +27,7 @@ crate::internal_macros::impl_op_for_references! {
|
|||
impl ops::Add<Amount> for 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 {
|
||||
type Output = NumOpResult<Amount>;
|
||||
|
@ -137,7 +38,7 @@ crate::internal_macros::impl_op_for_references! {
|
|||
impl ops::Sub<Amount> for 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 {
|
||||
type Output = NumOpResult<Amount>;
|
||||
|
@ -153,7 +54,7 @@ crate::internal_macros::impl_op_for_references! {
|
|||
impl ops::Mul<u64> for 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> {
|
||||
type Output = NumOpResult<Amount>;
|
||||
|
@ -163,7 +64,7 @@ crate::internal_macros::impl_op_for_references! {
|
|||
impl ops::Mul<Amount> for u64 {
|
||||
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 {
|
||||
type Output = NumOpResult<Amount>;
|
||||
|
@ -174,7 +75,7 @@ crate::internal_macros::impl_op_for_references! {
|
|||
impl ops::Div<u64> for 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> {
|
||||
type Output = NumOpResult<Amount>;
|
||||
|
@ -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<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(MathOp::Div)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Rem<u64> for 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> {
|
||||
type Output = NumOpResult<Amount>;
|
||||
|
@ -201,7 +104,7 @@ crate::internal_macros::impl_op_for_references! {
|
|||
impl ops::Add<SignedAmount> for 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 {
|
||||
type Output = NumOpResult<SignedAmount>;
|
||||
|
@ -212,7 +115,7 @@ crate::internal_macros::impl_op_for_references! {
|
|||
impl ops::Sub<SignedAmount> for 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 {
|
||||
type Output = NumOpResult<SignedAmount>;
|
||||
|
@ -228,7 +131,7 @@ crate::internal_macros::impl_op_for_references! {
|
|||
impl ops::Mul<i64> for 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> {
|
||||
type Output = NumOpResult<SignedAmount>;
|
||||
|
@ -238,7 +141,7 @@ crate::internal_macros::impl_op_for_references! {
|
|||
impl ops::Mul<SignedAmount> for i64 {
|
||||
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 {
|
||||
type Output = NumOpResult<SignedAmount>;
|
||||
|
@ -249,7 +152,7 @@ crate::internal_macros::impl_op_for_references! {
|
|||
impl ops::Div<i64> for 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> {
|
||||
type Output = NumOpResult<SignedAmount>;
|
||||
|
@ -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<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(MathOp::Div)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Rem<i64> for 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> {
|
||||
type Output = NumOpResult<SignedAmount>;
|
||||
|
@ -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<NumOpResult<Amount>> for NumOpResult<Amount> {
|
|||
{
|
||||
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<Amount>> for NumOpResult<Amount> {
|
|||
{
|
||||
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<NumOpResult<SignedAmount>> for NumOpResult<SignedAmount> {
|
|||
{
|
||||
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<SignedAmount>> for NumOpResult<SignedAm
|
|||
{
|
||||
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)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait OptionExt<T> {
|
||||
fn valid_or_error(self) -> NumOpResult<T>;
|
||||
}
|
||||
|
||||
impl OptionExt<Amount> for Option<Amount> {
|
||||
#[inline]
|
||||
fn valid_or_error(self) -> NumOpResult<Amount> {
|
||||
match self {
|
||||
Some(amount) => R::Valid(amount),
|
||||
None => R::Error(NumOpError {}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OptionExt<SignedAmount> for Option<SignedAmount> {
|
||||
#[inline]
|
||||
fn valid_or_error(self) -> NumOpResult<SignedAmount> {
|
||||
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 {}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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<FeeRate> for Weight {
|
||||
type Output = NumOpResult<Amount>;
|
||||
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 {
|
||||
type Output = NumOpResult<Amount>;
|
||||
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<Weight> for Amount {
|
||||
type Output = FeeRate;
|
||||
type Output = NumOpResult<FeeRate>;
|
||||
|
||||
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<FeeRate> for Amount {
|
||||
type Output = Weight;
|
||||
type Output = NumOpResult<Weight>;
|
||||
|
||||
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
|
||||
|
|
|
@ -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`:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<T> {
|
||||
/// Result of a successful mathematical operation.
|
||||
Valid(T),
|
||||
/// Result of an unsuccessful mathematical operation.
|
||||
Error(NumOpError),
|
||||
}
|
||||
|
||||
impl<T: fmt::Debug> NumOpResult<T> {
|
||||
/// 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<T>`.
|
||||
#[inline]
|
||||
pub fn ok(self) -> Option<T> {
|
||||
match self {
|
||||
R::Valid(x) => Some(x),
|
||||
R::Error(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts this `NumOpResult` to a `Result<T, NumOpError>`.
|
||||
#[inline]
|
||||
#[allow(clippy::missing_errors_doc)]
|
||||
pub fn into_result(self) -> Result<T, NumOpError> {
|
||||
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<F>(self, op: F) -> NumOpResult<T>
|
||||
where
|
||||
F: FnOnce(T) -> NumOpResult<T>,
|
||||
{
|
||||
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<T> {
|
||||
fn valid_or_error(self, op: MathOp) -> NumOpResult<T>;
|
||||
}
|
||||
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue