amount: Move SignedAmount to private signed module
In an effort to make the `amount` module more readable move the `SignedAmount` type to a private submodule. Re-export everything so this is not a breaking change. Code move and re-exports only.
This commit is contained in:
parent
0fc0e8760b
commit
d4d9311603
|
@ -9,20 +9,16 @@ pub mod error;
|
|||
#[cfg(feature = "serde")]
|
||||
pub mod serde;
|
||||
|
||||
mod signed;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod unsigned;
|
||||
#[cfg(kani)]
|
||||
mod verification;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::string::{String, ToString};
|
||||
use core::cmp::Ordering;
|
||||
use core::fmt;
|
||||
use core::str::FromStr;
|
||||
use core::{default, fmt, ops};
|
||||
|
||||
#[cfg(feature = "arbitrary")]
|
||||
use arbitrary::{Arbitrary, Unstructured};
|
||||
|
||||
use self::error::MissingDigitsKind;
|
||||
|
||||
|
@ -34,6 +30,7 @@ pub use self::{
|
|||
OutOfRangeError, ParseAmountError, ParseDenominationError, ParseError,
|
||||
PossiblyConfusingDenominationError, TooPreciseError, UnknownDenominationError,
|
||||
},
|
||||
signed::SignedAmount,
|
||||
unsigned::Amount,
|
||||
};
|
||||
|
||||
|
@ -562,397 +559,6 @@ enum DisplayStyle {
|
|||
DynamicDenomination,
|
||||
}
|
||||
|
||||
/// A signed amount.
|
||||
///
|
||||
/// The [`SignedAmount`] type can be used to express Bitcoin amounts that support
|
||||
/// arithmetic and conversion to various denominations.
|
||||
///
|
||||
///
|
||||
/// Warning!
|
||||
///
|
||||
/// This type implements several arithmetic operations from [`core::ops`].
|
||||
/// To prevent errors due to overflow or underflow when using these operations,
|
||||
/// it is advised to instead use the checked arithmetic methods whose names
|
||||
/// start with `checked_`. The operations from [`core::ops`] that [`Amount`]
|
||||
/// implements will panic when overflow or underflow occurs.
|
||||
///
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct SignedAmount(i64);
|
||||
|
||||
impl SignedAmount {
|
||||
/// The zero amount.
|
||||
pub const ZERO: SignedAmount = SignedAmount(0);
|
||||
/// Exactly one satoshi.
|
||||
pub const ONE_SAT: SignedAmount = SignedAmount(1);
|
||||
/// Exactly one bitcoin.
|
||||
pub const ONE_BTC: SignedAmount = SignedAmount(100_000_000);
|
||||
/// The maximum value allowed as an amount. Useful for sanity checking.
|
||||
pub const MAX_MONEY: SignedAmount = SignedAmount(21_000_000 * 100_000_000);
|
||||
/// The minimum value of an amount.
|
||||
pub const MIN: SignedAmount = SignedAmount(i64::MIN);
|
||||
/// The maximum value of an amount.
|
||||
pub const MAX: SignedAmount = SignedAmount(i64::MAX);
|
||||
|
||||
/// Create an [`SignedAmount`] with satoshi precision and the given number of satoshis.
|
||||
pub const fn from_sat(satoshi: i64) -> SignedAmount { SignedAmount(satoshi) }
|
||||
|
||||
/// Gets the number of satoshis in this [`SignedAmount`].
|
||||
pub const fn to_sat(self) -> i64 { self.0 }
|
||||
|
||||
/// Convert from a value expressing bitcoins to an [`SignedAmount`].
|
||||
#[cfg(feature = "alloc")]
|
||||
pub fn from_btc(btc: f64) -> Result<SignedAmount, ParseAmountError> {
|
||||
SignedAmount::from_float_in(btc, Denomination::Bitcoin)
|
||||
}
|
||||
|
||||
/// Parse a decimal string as a value in the given denomination.
|
||||
///
|
||||
/// Note: This only parses the value string. If you want to parse a value
|
||||
/// with denomination, use [`FromStr`].
|
||||
pub fn from_str_in(s: &str, denom: Denomination) -> Result<SignedAmount, ParseAmountError> {
|
||||
match parse_signed_to_satoshi(s, denom).map_err(|error| error.convert(true))? {
|
||||
// (negative, amount)
|
||||
(false, sat) if sat > i64::MAX as u64 =>
|
||||
Err(ParseAmountError::OutOfRange(OutOfRangeError::too_big(true))),
|
||||
(false, sat) => Ok(SignedAmount(sat as i64)),
|
||||
(true, sat) if sat == i64::MIN.unsigned_abs() => Ok(SignedAmount(i64::MIN)),
|
||||
(true, sat) if sat > i64::MIN.unsigned_abs() =>
|
||||
Err(ParseAmountError::OutOfRange(OutOfRangeError::too_small())),
|
||||
(true, sat) => Ok(SignedAmount(-(sat as i64))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses amounts with denomination suffix like they are produced with
|
||||
/// [`Self::to_string_with_denomination`] or with [`fmt::Display`].
|
||||
/// If you want to parse only the amount without the denomination,
|
||||
/// use [`Self::from_str_in`].
|
||||
pub fn from_str_with_denomination(s: &str) -> Result<SignedAmount, ParseError> {
|
||||
let (amt, denom) = split_amount_and_denomination(s)?;
|
||||
SignedAmount::from_str_in(amt, denom).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Express this [`SignedAmount`] as a floating-point value in the given denomination.
|
||||
///
|
||||
/// Please be aware of the risk of using floating-point numbers.
|
||||
#[cfg(feature = "alloc")]
|
||||
pub fn to_float_in(self, denom: Denomination) -> f64 {
|
||||
self.to_string_in(denom).parse::<f64>().unwrap()
|
||||
}
|
||||
|
||||
/// Express this [`SignedAmount`] as a floating-point value in Bitcoin.
|
||||
///
|
||||
/// Equivalent to `to_float_in(Denomination::Bitcoin)`.
|
||||
///
|
||||
/// Please be aware of the risk of using floating-point numbers.
|
||||
#[cfg(feature = "alloc")]
|
||||
pub fn to_btc(self) -> f64 { self.to_float_in(Denomination::Bitcoin) }
|
||||
|
||||
/// Convert this [`SignedAmount`] in floating-point notation with a given
|
||||
/// denomination.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If the amount is too big, too precise or negative.
|
||||
///
|
||||
/// Please be aware of the risk of using floating-point numbers.
|
||||
#[cfg(feature = "alloc")]
|
||||
pub fn from_float_in(
|
||||
value: f64,
|
||||
denom: Denomination,
|
||||
) -> Result<SignedAmount, ParseAmountError> {
|
||||
// This is inefficient, but the safest way to deal with this. The parsing logic is safe.
|
||||
// Any performance-critical application should not be dealing with floats.
|
||||
SignedAmount::from_str_in(&value.to_string(), denom)
|
||||
}
|
||||
|
||||
/// Create an object that implements [`fmt::Display`] using specified denomination.
|
||||
pub fn display_in(self, denomination: Denomination) -> Display {
|
||||
Display {
|
||||
sats_abs: self.unsigned_abs().to_sat(),
|
||||
is_negative: self.is_negative(),
|
||||
style: DisplayStyle::FixedDenomination { denomination, show_denomination: false },
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an object that implements [`fmt::Display`] dynamically selecting denomination.
|
||||
///
|
||||
/// This will use BTC for values greater than or equal to 1 BTC and satoshis otherwise. To
|
||||
/// avoid confusion the denomination is always shown.
|
||||
pub fn display_dynamic(self) -> Display {
|
||||
Display {
|
||||
sats_abs: self.unsigned_abs().to_sat(),
|
||||
is_negative: self.is_negative(),
|
||||
style: DisplayStyle::DynamicDenomination,
|
||||
}
|
||||
}
|
||||
|
||||
/// Format the value of this [`SignedAmount`] in the given denomination.
|
||||
///
|
||||
/// Does not include the denomination.
|
||||
#[rustfmt::skip]
|
||||
#[deprecated(since = "TBD", note = "use `display_in()` instead")]
|
||||
pub fn fmt_value_in(self, f: &mut dyn fmt::Write, denom: Denomination) -> fmt::Result {
|
||||
fmt_satoshi_in(self.unsigned_abs().to_sat(), self.is_negative(), f, denom, false, FormatOptions::default())
|
||||
}
|
||||
|
||||
/// Get a string number of this [`SignedAmount`] in the given denomination.
|
||||
///
|
||||
/// Does not include the denomination.
|
||||
#[cfg(feature = "alloc")]
|
||||
pub fn to_string_in(self, denom: Denomination) -> String { self.display_in(denom).to_string() }
|
||||
|
||||
/// Get a formatted string of this [`SignedAmount`] in the given denomination,
|
||||
/// suffixed with the abbreviation for the denomination.
|
||||
#[cfg(feature = "alloc")]
|
||||
pub fn to_string_with_denomination(self, denom: Denomination) -> String {
|
||||
self.display_in(denom).show_denomination().to_string()
|
||||
}
|
||||
|
||||
// Some arithmetic that doesn't fit in [`core::ops`] traits.
|
||||
|
||||
/// Get the absolute value of this [`SignedAmount`].
|
||||
pub fn abs(self) -> SignedAmount { SignedAmount(self.0.abs()) }
|
||||
|
||||
/// Get the absolute value of this [`SignedAmount`] returning [`Amount`].
|
||||
pub fn unsigned_abs(self) -> Amount { Amount::from_sat(self.0.unsigned_abs()) }
|
||||
|
||||
/// Returns a number representing sign of this [`SignedAmount`].
|
||||
///
|
||||
/// - `0` if the amount is zero
|
||||
/// - `1` if the amount is positive
|
||||
/// - `-1` if the amount is negative
|
||||
pub fn signum(self) -> i64 { self.0.signum() }
|
||||
|
||||
/// Checks if this [`SignedAmount`] is positive.
|
||||
///
|
||||
/// Returns `true` if this [`SignedAmount`] is positive and `false` if
|
||||
/// this [`SignedAmount`] is zero or negative.
|
||||
pub fn is_positive(self) -> bool { self.0.is_positive() }
|
||||
|
||||
/// Checks if this [`SignedAmount`] is negative.
|
||||
///
|
||||
/// Returns `true` if this [`SignedAmount`] is negative and `false` if
|
||||
/// this [`SignedAmount`] is zero or positive.
|
||||
pub fn is_negative(self) -> bool { self.0.is_negative() }
|
||||
|
||||
/// Returns the absolute value of this [`SignedAmount`].
|
||||
///
|
||||
/// Consider using `unsigned_abs` which is often more practical.
|
||||
///
|
||||
/// Returns [`None`] if overflow occurred. (`self == MIN`)
|
||||
pub fn checked_abs(self) -> Option<SignedAmount> { self.0.checked_abs().map(SignedAmount) }
|
||||
|
||||
/// Checked addition.
|
||||
///
|
||||
/// Returns [`None`] if overflow occurred.
|
||||
pub fn checked_add(self, rhs: SignedAmount) -> Option<SignedAmount> {
|
||||
self.0.checked_add(rhs.0).map(SignedAmount)
|
||||
}
|
||||
|
||||
/// Checked subtraction.
|
||||
///
|
||||
/// Returns [`None`] if overflow occurred.
|
||||
pub fn checked_sub(self, rhs: SignedAmount) -> Option<SignedAmount> {
|
||||
self.0.checked_sub(rhs.0).map(SignedAmount)
|
||||
}
|
||||
|
||||
/// Checked multiplication.
|
||||
///
|
||||
/// Returns [`None`] if overflow occurred.
|
||||
pub fn checked_mul(self, rhs: i64) -> Option<SignedAmount> {
|
||||
self.0.checked_mul(rhs).map(SignedAmount)
|
||||
}
|
||||
|
||||
/// Checked integer division.
|
||||
///
|
||||
/// Be aware that integer division loses the remainder if no exact division
|
||||
/// can be made.
|
||||
///
|
||||
/// Returns [`None`] if overflow occurred.
|
||||
pub fn checked_div(self, rhs: i64) -> Option<SignedAmount> {
|
||||
self.0.checked_div(rhs).map(SignedAmount)
|
||||
}
|
||||
|
||||
/// Checked remainder.
|
||||
///
|
||||
/// Returns [`None`] if overflow occurred.
|
||||
pub fn checked_rem(self, rhs: i64) -> Option<SignedAmount> {
|
||||
self.0.checked_rem(rhs).map(SignedAmount)
|
||||
}
|
||||
|
||||
/// Unchecked addition.
|
||||
///
|
||||
/// Computes `self + rhs`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// On overflow, panics in debug mode, wraps in release mode.
|
||||
pub fn unchecked_add(self, rhs: SignedAmount) -> SignedAmount { Self(self.0 + rhs.0) }
|
||||
|
||||
/// Unchecked subtraction.
|
||||
///
|
||||
/// Computes `self - rhs`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// On overflow, panics in debug mode, wraps in release mode.
|
||||
pub fn unchecked_sub(self, rhs: SignedAmount) -> SignedAmount { Self(self.0 - rhs.0) }
|
||||
|
||||
/// Subtraction that doesn't allow negative [`SignedAmount`]s.
|
||||
///
|
||||
/// Returns [`None`] if either `self`, `rhs` or the result is strictly negative.
|
||||
pub fn positive_sub(self, rhs: SignedAmount) -> Option<SignedAmount> {
|
||||
if self.is_negative() || rhs.is_negative() || rhs > self {
|
||||
None
|
||||
} else {
|
||||
self.checked_sub(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts to an unsigned amount.
|
||||
pub fn to_unsigned(self) -> Result<Amount, OutOfRangeError> {
|
||||
if self.is_negative() {
|
||||
Err(OutOfRangeError::negative())
|
||||
} else {
|
||||
Ok(Amount::from_sat(self.to_sat() as u64))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl default::Default for SignedAmount {
|
||||
fn default() -> Self { SignedAmount::ZERO }
|
||||
}
|
||||
|
||||
impl fmt::Debug for SignedAmount {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "SignedAmount({} SAT)", self.to_sat())
|
||||
}
|
||||
}
|
||||
|
||||
// No one should depend on a binding contract for Display for this type.
|
||||
// Just using Bitcoin denominated string.
|
||||
impl fmt::Display for SignedAmount {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.display_in(Denomination::Bitcoin).show_denomination(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Add for SignedAmount {
|
||||
type Output = SignedAmount;
|
||||
|
||||
fn add(self, rhs: SignedAmount) -> Self::Output {
|
||||
self.checked_add(rhs).expect("SignedAmount addition error")
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::AddAssign for SignedAmount {
|
||||
fn add_assign(&mut self, other: SignedAmount) { *self = *self + other }
|
||||
}
|
||||
|
||||
impl ops::Sub for SignedAmount {
|
||||
type Output = SignedAmount;
|
||||
|
||||
fn sub(self, rhs: SignedAmount) -> Self::Output {
|
||||
self.checked_sub(rhs).expect("SignedAmount subtraction error")
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::SubAssign for SignedAmount {
|
||||
fn sub_assign(&mut self, other: SignedAmount) { *self = *self - other }
|
||||
}
|
||||
|
||||
impl ops::Rem<i64> for SignedAmount {
|
||||
type Output = SignedAmount;
|
||||
|
||||
fn rem(self, modulus: i64) -> Self {
|
||||
self.checked_rem(modulus).expect("SignedAmount remainder error")
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::RemAssign<i64> for SignedAmount {
|
||||
fn rem_assign(&mut self, modulus: i64) { *self = *self % modulus }
|
||||
}
|
||||
|
||||
impl ops::Mul<i64> for SignedAmount {
|
||||
type Output = SignedAmount;
|
||||
|
||||
fn mul(self, rhs: i64) -> Self::Output {
|
||||
self.checked_mul(rhs).expect("SignedAmount multiplication error")
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::MulAssign<i64> for SignedAmount {
|
||||
fn mul_assign(&mut self, rhs: i64) { *self = *self * rhs }
|
||||
}
|
||||
|
||||
impl ops::Div<i64> for SignedAmount {
|
||||
type Output = SignedAmount;
|
||||
|
||||
fn div(self, rhs: i64) -> Self::Output {
|
||||
self.checked_div(rhs).expect("SignedAmount division error")
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::DivAssign<i64> for SignedAmount {
|
||||
fn div_assign(&mut self, rhs: i64) { *self = *self / rhs }
|
||||
}
|
||||
|
||||
impl ops::Neg for SignedAmount {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self::Output { Self(self.0.neg()) }
|
||||
}
|
||||
|
||||
impl FromStr for SignedAmount {
|
||||
type Err = ParseError;
|
||||
|
||||
/// Parses a string slice where the slice includes a denomination.
|
||||
///
|
||||
/// If the string slice is zero or negative zero, then no denomination is required.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// `Ok(Amount)` if the string amount and denomination parse successfully,
|
||||
/// otherwise, return `Err(ParseError)`.
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let result = SignedAmount::from_str_with_denomination(s);
|
||||
|
||||
match result {
|
||||
Err(ParseError::MissingDenomination(_)) => {
|
||||
let d = SignedAmount::from_str_in(s, Denomination::Satoshi);
|
||||
|
||||
if d == Ok(SignedAmount::ZERO) {
|
||||
Ok(SignedAmount::ZERO)
|
||||
} else {
|
||||
result
|
||||
}
|
||||
}
|
||||
_ => result,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Amount> for SignedAmount {
|
||||
type Error = OutOfRangeError;
|
||||
|
||||
fn try_from(value: Amount) -> Result<Self, Self::Error> { value.to_signed() }
|
||||
}
|
||||
|
||||
impl core::iter::Sum for SignedAmount {
|
||||
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
|
||||
let sats: i64 = iter.map(|amt| amt.0).sum();
|
||||
SignedAmount::from_sat(sats)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "arbitrary")]
|
||||
impl<'a> Arbitrary<'a> for SignedAmount {
|
||||
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
|
||||
let s = i64::arbitrary(u)?;
|
||||
Ok(SignedAmount(s))
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the sum over the iterator using checked arithmetic.
|
||||
pub trait CheckedSum<R>: private::SumSeal<R> {
|
||||
/// Calculates the sum over the iterator using checked arithmetic. If an over or underflow would
|
||||
|
|
|
@ -0,0 +1,407 @@
|
|||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
//! A signed bitcoin amount.
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use alloc::string::{String, ToString};
|
||||
use core::str::FromStr;
|
||||
use core::{default, fmt, ops};
|
||||
|
||||
#[cfg(feature = "arbitrary")]
|
||||
use arbitrary::{Arbitrary, Unstructured};
|
||||
|
||||
use super::{
|
||||
fmt_satoshi_in, parse_signed_to_satoshi, split_amount_and_denomination, Amount, Denomination,
|
||||
Display, DisplayStyle, FormatOptions, OutOfRangeError, ParseAmountError, ParseError,
|
||||
};
|
||||
|
||||
/// A signed amount.
|
||||
///
|
||||
/// The [`SignedAmount`] type can be used to express Bitcoin amounts that support
|
||||
/// arithmetic and conversion to various denominations.
|
||||
///
|
||||
///
|
||||
/// Warning!
|
||||
///
|
||||
/// This type implements several arithmetic operations from [`core::ops`].
|
||||
/// To prevent errors due to overflow or underflow when using these operations,
|
||||
/// it is advised to instead use the checked arithmetic methods whose names
|
||||
/// start with `checked_`. The operations from [`core::ops`] that [`Amount`]
|
||||
/// implements will panic when overflow or underflow occurs.
|
||||
///
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct SignedAmount(i64);
|
||||
|
||||
impl SignedAmount {
|
||||
/// The zero amount.
|
||||
pub const ZERO: SignedAmount = SignedAmount(0);
|
||||
/// Exactly one satoshi.
|
||||
pub const ONE_SAT: SignedAmount = SignedAmount(1);
|
||||
/// Exactly one bitcoin.
|
||||
pub const ONE_BTC: SignedAmount = SignedAmount(100_000_000);
|
||||
/// The maximum value allowed as an amount. Useful for sanity checking.
|
||||
pub const MAX_MONEY: SignedAmount = SignedAmount(21_000_000 * 100_000_000);
|
||||
/// The minimum value of an amount.
|
||||
pub const MIN: SignedAmount = SignedAmount(i64::MIN);
|
||||
/// The maximum value of an amount.
|
||||
pub const MAX: SignedAmount = SignedAmount(i64::MAX);
|
||||
|
||||
/// Create an [`SignedAmount`] with satoshi precision and the given number of satoshis.
|
||||
pub const fn from_sat(satoshi: i64) -> SignedAmount { SignedAmount(satoshi) }
|
||||
|
||||
/// Gets the number of satoshis in this [`SignedAmount`].
|
||||
pub const fn to_sat(self) -> i64 { self.0 }
|
||||
|
||||
/// Convert from a value expressing bitcoins to an [`SignedAmount`].
|
||||
#[cfg(feature = "alloc")]
|
||||
pub fn from_btc(btc: f64) -> Result<SignedAmount, ParseAmountError> {
|
||||
SignedAmount::from_float_in(btc, Denomination::Bitcoin)
|
||||
}
|
||||
|
||||
/// Parse a decimal string as a value in the given denomination.
|
||||
///
|
||||
/// Note: This only parses the value string. If you want to parse a value
|
||||
/// with denomination, use [`FromStr`].
|
||||
pub fn from_str_in(s: &str, denom: Denomination) -> Result<SignedAmount, ParseAmountError> {
|
||||
match parse_signed_to_satoshi(s, denom).map_err(|error| error.convert(true))? {
|
||||
// (negative, amount)
|
||||
(false, sat) if sat > i64::MAX as u64 =>
|
||||
Err(ParseAmountError::OutOfRange(OutOfRangeError::too_big(true))),
|
||||
(false, sat) => Ok(SignedAmount(sat as i64)),
|
||||
(true, sat) if sat == i64::MIN.unsigned_abs() => Ok(SignedAmount(i64::MIN)),
|
||||
(true, sat) if sat > i64::MIN.unsigned_abs() =>
|
||||
Err(ParseAmountError::OutOfRange(OutOfRangeError::too_small())),
|
||||
(true, sat) => Ok(SignedAmount(-(sat as i64))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses amounts with denomination suffix like they are produced with
|
||||
/// [`Self::to_string_with_denomination`] or with [`fmt::Display`].
|
||||
/// If you want to parse only the amount without the denomination,
|
||||
/// use [`Self::from_str_in`].
|
||||
pub fn from_str_with_denomination(s: &str) -> Result<SignedAmount, ParseError> {
|
||||
let (amt, denom) = split_amount_and_denomination(s)?;
|
||||
SignedAmount::from_str_in(amt, denom).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Express this [`SignedAmount`] as a floating-point value in the given denomination.
|
||||
///
|
||||
/// Please be aware of the risk of using floating-point numbers.
|
||||
#[cfg(feature = "alloc")]
|
||||
pub fn to_float_in(self, denom: Denomination) -> f64 {
|
||||
self.to_string_in(denom).parse::<f64>().unwrap()
|
||||
}
|
||||
|
||||
/// Express this [`SignedAmount`] as a floating-point value in Bitcoin.
|
||||
///
|
||||
/// Equivalent to `to_float_in(Denomination::Bitcoin)`.
|
||||
///
|
||||
/// Please be aware of the risk of using floating-point numbers.
|
||||
#[cfg(feature = "alloc")]
|
||||
pub fn to_btc(self) -> f64 { self.to_float_in(Denomination::Bitcoin) }
|
||||
|
||||
/// Convert this [`SignedAmount`] in floating-point notation with a given
|
||||
/// denomination.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If the amount is too big, too precise or negative.
|
||||
///
|
||||
/// Please be aware of the risk of using floating-point numbers.
|
||||
#[cfg(feature = "alloc")]
|
||||
pub fn from_float_in(
|
||||
value: f64,
|
||||
denom: Denomination,
|
||||
) -> Result<SignedAmount, ParseAmountError> {
|
||||
// This is inefficient, but the safest way to deal with this. The parsing logic is safe.
|
||||
// Any performance-critical application should not be dealing with floats.
|
||||
SignedAmount::from_str_in(&value.to_string(), denom)
|
||||
}
|
||||
|
||||
/// Create an object that implements [`fmt::Display`] using specified denomination.
|
||||
pub fn display_in(self, denomination: Denomination) -> Display {
|
||||
Display {
|
||||
sats_abs: self.unsigned_abs().to_sat(),
|
||||
is_negative: self.is_negative(),
|
||||
style: DisplayStyle::FixedDenomination { denomination, show_denomination: false },
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an object that implements [`fmt::Display`] dynamically selecting denomination.
|
||||
///
|
||||
/// This will use BTC for values greater than or equal to 1 BTC and satoshis otherwise. To
|
||||
/// avoid confusion the denomination is always shown.
|
||||
pub fn display_dynamic(self) -> Display {
|
||||
Display {
|
||||
sats_abs: self.unsigned_abs().to_sat(),
|
||||
is_negative: self.is_negative(),
|
||||
style: DisplayStyle::DynamicDenomination,
|
||||
}
|
||||
}
|
||||
|
||||
/// Format the value of this [`SignedAmount`] in the given denomination.
|
||||
///
|
||||
/// Does not include the denomination.
|
||||
#[rustfmt::skip]
|
||||
#[deprecated(since = "TBD", note = "use `display_in()` instead")]
|
||||
pub fn fmt_value_in(self, f: &mut dyn fmt::Write, denom: Denomination) -> fmt::Result {
|
||||
fmt_satoshi_in(self.unsigned_abs().to_sat(), self.is_negative(), f, denom, false, FormatOptions::default())
|
||||
}
|
||||
|
||||
/// Get a string number of this [`SignedAmount`] in the given denomination.
|
||||
///
|
||||
/// Does not include the denomination.
|
||||
#[cfg(feature = "alloc")]
|
||||
pub fn to_string_in(self, denom: Denomination) -> String { self.display_in(denom).to_string() }
|
||||
|
||||
/// Get a formatted string of this [`SignedAmount`] in the given denomination,
|
||||
/// suffixed with the abbreviation for the denomination.
|
||||
#[cfg(feature = "alloc")]
|
||||
pub fn to_string_with_denomination(self, denom: Denomination) -> String {
|
||||
self.display_in(denom).show_denomination().to_string()
|
||||
}
|
||||
|
||||
// Some arithmetic that doesn't fit in [`core::ops`] traits.
|
||||
|
||||
/// Get the absolute value of this [`SignedAmount`].
|
||||
pub fn abs(self) -> SignedAmount { SignedAmount(self.0.abs()) }
|
||||
|
||||
/// Get the absolute value of this [`SignedAmount`] returning [`Amount`].
|
||||
pub fn unsigned_abs(self) -> Amount { Amount::from_sat(self.0.unsigned_abs()) }
|
||||
|
||||
/// Returns a number representing sign of this [`SignedAmount`].
|
||||
///
|
||||
/// - `0` if the amount is zero
|
||||
/// - `1` if the amount is positive
|
||||
/// - `-1` if the amount is negative
|
||||
pub fn signum(self) -> i64 { self.0.signum() }
|
||||
|
||||
/// Checks if this [`SignedAmount`] is positive.
|
||||
///
|
||||
/// Returns `true` if this [`SignedAmount`] is positive and `false` if
|
||||
/// this [`SignedAmount`] is zero or negative.
|
||||
pub fn is_positive(self) -> bool { self.0.is_positive() }
|
||||
|
||||
/// Checks if this [`SignedAmount`] is negative.
|
||||
///
|
||||
/// Returns `true` if this [`SignedAmount`] is negative and `false` if
|
||||
/// this [`SignedAmount`] is zero or positive.
|
||||
pub fn is_negative(self) -> bool { self.0.is_negative() }
|
||||
|
||||
/// Returns the absolute value of this [`SignedAmount`].
|
||||
///
|
||||
/// Consider using `unsigned_abs` which is often more practical.
|
||||
///
|
||||
/// Returns [`None`] if overflow occurred. (`self == MIN`)
|
||||
pub fn checked_abs(self) -> Option<SignedAmount> { self.0.checked_abs().map(SignedAmount) }
|
||||
|
||||
/// Checked addition.
|
||||
///
|
||||
/// Returns [`None`] if overflow occurred.
|
||||
pub fn checked_add(self, rhs: SignedAmount) -> Option<SignedAmount> {
|
||||
self.0.checked_add(rhs.0).map(SignedAmount)
|
||||
}
|
||||
|
||||
/// Checked subtraction.
|
||||
///
|
||||
/// Returns [`None`] if overflow occurred.
|
||||
pub fn checked_sub(self, rhs: SignedAmount) -> Option<SignedAmount> {
|
||||
self.0.checked_sub(rhs.0).map(SignedAmount)
|
||||
}
|
||||
|
||||
/// Checked multiplication.
|
||||
///
|
||||
/// Returns [`None`] if overflow occurred.
|
||||
pub fn checked_mul(self, rhs: i64) -> Option<SignedAmount> {
|
||||
self.0.checked_mul(rhs).map(SignedAmount)
|
||||
}
|
||||
|
||||
/// Checked integer division.
|
||||
///
|
||||
/// Be aware that integer division loses the remainder if no exact division
|
||||
/// can be made.
|
||||
///
|
||||
/// Returns [`None`] if overflow occurred.
|
||||
pub fn checked_div(self, rhs: i64) -> Option<SignedAmount> {
|
||||
self.0.checked_div(rhs).map(SignedAmount)
|
||||
}
|
||||
|
||||
/// Checked remainder.
|
||||
///
|
||||
/// Returns [`None`] if overflow occurred.
|
||||
pub fn checked_rem(self, rhs: i64) -> Option<SignedAmount> {
|
||||
self.0.checked_rem(rhs).map(SignedAmount)
|
||||
}
|
||||
|
||||
/// Unchecked addition.
|
||||
///
|
||||
/// Computes `self + rhs`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// On overflow, panics in debug mode, wraps in release mode.
|
||||
pub fn unchecked_add(self, rhs: SignedAmount) -> SignedAmount { Self(self.0 + rhs.0) }
|
||||
|
||||
/// Unchecked subtraction.
|
||||
///
|
||||
/// Computes `self - rhs`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// On overflow, panics in debug mode, wraps in release mode.
|
||||
pub fn unchecked_sub(self, rhs: SignedAmount) -> SignedAmount { Self(self.0 - rhs.0) }
|
||||
|
||||
/// Subtraction that doesn't allow negative [`SignedAmount`]s.
|
||||
///
|
||||
/// Returns [`None`] if either `self`, `rhs` or the result is strictly negative.
|
||||
pub fn positive_sub(self, rhs: SignedAmount) -> Option<SignedAmount> {
|
||||
if self.is_negative() || rhs.is_negative() || rhs > self {
|
||||
None
|
||||
} else {
|
||||
self.checked_sub(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts to an unsigned amount.
|
||||
pub fn to_unsigned(self) -> Result<Amount, OutOfRangeError> {
|
||||
if self.is_negative() {
|
||||
Err(OutOfRangeError::negative())
|
||||
} else {
|
||||
Ok(Amount::from_sat(self.to_sat() as u64))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl default::Default for SignedAmount {
|
||||
fn default() -> Self { SignedAmount::ZERO }
|
||||
}
|
||||
|
||||
impl fmt::Debug for SignedAmount {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "SignedAmount({} SAT)", self.to_sat())
|
||||
}
|
||||
}
|
||||
|
||||
// No one should depend on a binding contract for Display for this type.
|
||||
// Just using Bitcoin denominated string.
|
||||
impl fmt::Display for SignedAmount {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.display_in(Denomination::Bitcoin).show_denomination(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Add for SignedAmount {
|
||||
type Output = SignedAmount;
|
||||
|
||||
fn add(self, rhs: SignedAmount) -> Self::Output {
|
||||
self.checked_add(rhs).expect("SignedAmount addition error")
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::AddAssign for SignedAmount {
|
||||
fn add_assign(&mut self, other: SignedAmount) { *self = *self + other }
|
||||
}
|
||||
|
||||
impl ops::Sub for SignedAmount {
|
||||
type Output = SignedAmount;
|
||||
|
||||
fn sub(self, rhs: SignedAmount) -> Self::Output {
|
||||
self.checked_sub(rhs).expect("SignedAmount subtraction error")
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::SubAssign for SignedAmount {
|
||||
fn sub_assign(&mut self, other: SignedAmount) { *self = *self - other }
|
||||
}
|
||||
|
||||
impl ops::Rem<i64> for SignedAmount {
|
||||
type Output = SignedAmount;
|
||||
|
||||
fn rem(self, modulus: i64) -> Self {
|
||||
self.checked_rem(modulus).expect("SignedAmount remainder error")
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::RemAssign<i64> for SignedAmount {
|
||||
fn rem_assign(&mut self, modulus: i64) { *self = *self % modulus }
|
||||
}
|
||||
|
||||
impl ops::Mul<i64> for SignedAmount {
|
||||
type Output = SignedAmount;
|
||||
|
||||
fn mul(self, rhs: i64) -> Self::Output {
|
||||
self.checked_mul(rhs).expect("SignedAmount multiplication error")
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::MulAssign<i64> for SignedAmount {
|
||||
fn mul_assign(&mut self, rhs: i64) { *self = *self * rhs }
|
||||
}
|
||||
|
||||
impl ops::Div<i64> for SignedAmount {
|
||||
type Output = SignedAmount;
|
||||
|
||||
fn div(self, rhs: i64) -> Self::Output {
|
||||
self.checked_div(rhs).expect("SignedAmount division error")
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::DivAssign<i64> for SignedAmount {
|
||||
fn div_assign(&mut self, rhs: i64) { *self = *self / rhs }
|
||||
}
|
||||
|
||||
impl ops::Neg for SignedAmount {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self::Output { Self(self.0.neg()) }
|
||||
}
|
||||
|
||||
impl FromStr for SignedAmount {
|
||||
type Err = ParseError;
|
||||
|
||||
/// Parses a string slice where the slice includes a denomination.
|
||||
///
|
||||
/// If the string slice is zero or negative zero, then no denomination is required.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// `Ok(Amount)` if the string amount and denomination parse successfully,
|
||||
/// otherwise, return `Err(ParseError)`.
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let result = SignedAmount::from_str_with_denomination(s);
|
||||
|
||||
match result {
|
||||
Err(ParseError::MissingDenomination(_)) => {
|
||||
let d = SignedAmount::from_str_in(s, Denomination::Satoshi);
|
||||
|
||||
if d == Ok(SignedAmount::ZERO) {
|
||||
Ok(SignedAmount::ZERO)
|
||||
} else {
|
||||
result
|
||||
}
|
||||
}
|
||||
_ => result,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Amount> for SignedAmount {
|
||||
type Error = OutOfRangeError;
|
||||
|
||||
fn try_from(value: Amount) -> Result<Self, Self::Error> { value.to_signed() }
|
||||
}
|
||||
|
||||
impl core::iter::Sum for SignedAmount {
|
||||
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
|
||||
let sats: i64 = iter.map(|amt| amt.0).sum();
|
||||
SignedAmount::from_sat(sats)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "arbitrary")]
|
||||
impl<'a> Arbitrary<'a> for SignedAmount {
|
||||
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
|
||||
let s = i64::arbitrary(u)?;
|
||||
Ok(SignedAmount(s))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue