From e17c391a3c385802ba5fb203c68541e23b68e494 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Thu, 12 Jun 2025 10:16:29 +1000 Subject: [PATCH] Inline checked div functions back into unsigned module A while back we move all the 'fee' stuff into a separate module because I thought it would help with clarity - I was wrong. Move the checked div functions back into the `unsigned` module on the main `Amount` impl block. Internal change only - code move. --- units/src/amount/unsigned.rs | 101 ++++++++++++++++++++++++++++++++++ units/src/fee.rs | 102 ----------------------------------- 2 files changed, 101 insertions(+), 102 deletions(-) diff --git a/units/src/amount/unsigned.rs b/units/src/amount/unsigned.rs index 33c97b4fc..bbc126f91 100644 --- a/units/src/amount/unsigned.rs +++ b/units/src/amount/unsigned.rs @@ -15,6 +15,7 @@ use super::{ parse_signed_to_satoshi, split_amount_and_denomination, Denomination, Display, DisplayStyle, OutOfRangeError, ParseAmountError, ParseError, SignedAmount, }; +use crate::{FeeRate, Weight}; mod encapsulate { use super::OutOfRangeError; @@ -402,6 +403,106 @@ impl Amount { SignedAmount::from_sat(self.to_sat() as i64) // Cast ok, signed amount and amount share positive range. .expect("range of Amount is within range of SignedAmount") } + + /// Checked weight floor division. + /// + /// Be aware that integer division loses the remainder if no exact division + /// can be made. See also [`Self::checked_div_by_weight_ceil`]. + /// + /// Returns [`None`] if overflow occurred. + #[must_use] + pub const fn checked_div_by_weight_floor(self, weight: Weight) -> Option { + let wu = weight.to_wu(); + if wu == 0 { + return None; + } + + // Mul by 1,000 because we use per/kwu. + match self.to_sat().checked_mul(1_000) { + Some(sats) => { + let fee_rate = sats / wu; + FeeRate::from_sat_per_kwu(fee_rate) + } + None => None, + } + } + + /// Checked weight ceiling division. + /// + /// Be aware that integer division loses the remainder if no exact division + /// can be made. This method rounds up ensuring the transaction fee rate is + /// sufficient. See also [`Self::checked_div_by_weight_floor`]. + /// + /// Returns [`None`] if overflow occurred. + /// + /// # Examples + /// + /// ``` + /// # use bitcoin_units::{amount, Amount, FeeRate, Weight}; + /// let amount = Amount::from_sat(10)?; + /// let weight = Weight::from_wu(300); + /// let fee_rate = amount.checked_div_by_weight_ceil(weight); + /// assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(34)); + /// # Ok::<_, amount::OutOfRangeError>(()) + /// ``` + #[must_use] + pub const fn checked_div_by_weight_ceil(self, weight: Weight) -> Option { + let wu = weight.to_wu(); + if wu == 0 { + return None; + } + + // Mul by 1,000 because we use per/kwu. + if let Some(sats) = self.to_sat().checked_mul(1_000) { + // No need to used checked arithmetic because wu is non-zero. + if let Some(bump) = sats.checked_add(wu - 1) { + let fee_rate = bump / wu; + return FeeRate::from_sat_per_kwu(fee_rate); + } + } + None + } + + /// Checked fee rate floor division. + /// + /// Computes the maximum weight that would result in a fee less than or equal to this amount + /// at the given `fee_rate`. Uses floor division to ensure the resulting weight doesn't cause + /// the fee to exceed the amount. + /// + /// Returns [`None`] if overflow occurred or if `fee_rate` is zero. + #[must_use] + pub const fn checked_div_by_fee_rate_floor(self, fee_rate: FeeRate) -> Option { + if let Some(msats) = self.to_sat().checked_mul(1000) { + if let Some(wu) = msats.checked_div(fee_rate.to_sat_per_kwu_ceil()) { + return Some(Weight::from_wu(wu)); + } + } + None + } + + /// Checked fee rate ceiling division. + /// + /// Computes the minimum weight that would result in a fee greater than or equal to this amount + /// at the given `fee_rate`. Uses ceiling division to ensure the resulting weight is sufficient. + /// + /// Returns [`None`] if overflow occurred or if `fee_rate` is zero. + #[must_use] + pub const fn checked_div_by_fee_rate_ceil(self, fee_rate: FeeRate) -> Option { + // Use ceil because result is used as the divisor. + let rate = fee_rate.to_sat_per_kwu_ceil(); + if rate == 0 { + return None; + } + + if let Some(msats) = self.to_sat().checked_mul(1000) { + // No need to used checked arithmetic because rate is non-zero. + if let Some(bump) = msats.checked_add(rate - 1) { + let wu = bump / rate; + return Some(Weight::from_wu(wu)); + } + } + None + } } impl default::Default for Amount { diff --git a/units/src/fee.rs b/units/src/fee.rs index 8248a9957..1354fc848 100644 --- a/units/src/fee.rs +++ b/units/src/fee.rs @@ -17,108 +17,6 @@ use NumOpResult as R; use crate::{Amount, FeeRate, MathOp, NumOpError as E, NumOpResult, OptionExt, Weight}; -impl Amount { - /// Checked weight floor division. - /// - /// Be aware that integer division loses the remainder if no exact division - /// can be made. See also [`Self::checked_div_by_weight_ceil`]. - /// - /// Returns [`None`] if overflow occurred. - #[must_use] - pub const fn checked_div_by_weight_floor(self, weight: Weight) -> Option { - let wu = weight.to_wu(); - if wu == 0 { - return None; - } - - // Mul by 1,000 because we use per/kwu. - match self.to_sat().checked_mul(1_000) { - Some(sats) => { - let fee_rate = sats / wu; - FeeRate::from_sat_per_kwu(fee_rate) - } - None => None, - } - } - - /// Checked weight ceiling division. - /// - /// Be aware that integer division loses the remainder if no exact division - /// can be made. This method rounds up ensuring the transaction fee rate is - /// sufficient. See also [`Self::checked_div_by_weight_floor`]. - /// - /// Returns [`None`] if overflow occurred. - /// - /// # Examples - /// - /// ``` - /// # use bitcoin_units::{amount, Amount, FeeRate, Weight}; - /// let amount = Amount::from_sat(10)?; - /// let weight = Weight::from_wu(300); - /// let fee_rate = amount.checked_div_by_weight_ceil(weight); - /// assert_eq!(fee_rate, FeeRate::from_sat_per_kwu(34)); - /// # Ok::<_, amount::OutOfRangeError>(()) - /// ``` - #[must_use] - pub const fn checked_div_by_weight_ceil(self, weight: Weight) -> Option { - let wu = weight.to_wu(); - if wu == 0 { - return None; - } - - // Mul by 1,000 because we use per/kwu. - if let Some(sats) = self.to_sat().checked_mul(1_000) { - // No need to used checked arithmetic because wu is non-zero. - if let Some(bump) = sats.checked_add(wu - 1) { - let fee_rate = bump / wu; - return FeeRate::from_sat_per_kwu(fee_rate); - } - } - None - } - - /// Checked fee rate floor division. - /// - /// Computes the maximum weight that would result in a fee less than or equal to this amount - /// at the given `fee_rate`. Uses floor division to ensure the resulting weight doesn't cause - /// the fee to exceed the amount. - /// - /// Returns [`None`] if overflow occurred or if `fee_rate` is zero. - #[must_use] - pub const fn checked_div_by_fee_rate_floor(self, fee_rate: FeeRate) -> Option { - if let Some(msats) = self.to_sat().checked_mul(1000) { - if let Some(wu) = msats.checked_div(fee_rate.to_sat_per_kwu_ceil()) { - return Some(Weight::from_wu(wu)); - } - } - None - } - - /// Checked fee rate ceiling division. - /// - /// Computes the minimum weight that would result in a fee greater than or equal to this amount - /// at the given `fee_rate`. Uses ceiling division to ensure the resulting weight is sufficient. - /// - /// Returns [`None`] if overflow occurred or if `fee_rate` is zero. - #[must_use] - pub const fn checked_div_by_fee_rate_ceil(self, fee_rate: FeeRate) -> Option { - // Use ceil because result is used as the divisor. - let rate = fee_rate.to_sat_per_kwu_ceil(); - if rate == 0 { - return None; - } - - if let Some(msats) = self.to_sat().checked_mul(1000) { - // No need to used checked arithmetic because rate is non-zero. - if let Some(bump) = msats.checked_add(rate - 1) { - let wu = bump / rate; - return Some(Weight::from_wu(wu)); - } - } - None - } -} - impl FeeRate { /// Calculates the fee by multiplying this fee rate by weight. ///