// To the extent possible under law, the author(s) have dedicated all
// copyright and related and neighboring rights to this software to
// the public domain worldwide. This software is distributed without
// any warranty.
//
// You should have received a copy of the CC0 Public Domain Dedication
// along with this software.
// If not, see .
//
//! Bitcoin amounts.
//!
//! This module mainly introduces the [Amount] and [SignedAmount] types.
//! We refer to the documentation on the types for more information.
//!
use crate::prelude::*;
use core::{ops, default, str::FromStr, cmp::Ordering};
use core::fmt::{self, Write};
/// A set of denominations in which amounts can be expressed.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum Denomination {
/// BTC
Bitcoin,
/// mBTC
MilliBitcoin,
/// uBTC
MicroBitcoin,
/// nBTC
NanoBitcoin,
/// pBTC
PicoBitcoin,
/// bits
Bit,
/// satoshi
Satoshi,
/// msat
MilliSatoshi,
}
impl Denomination {
/// The number of decimal places more than a satoshi.
fn precision(self) -> i8 {
match self {
Denomination::Bitcoin => -8,
Denomination::MilliBitcoin => -5,
Denomination::MicroBitcoin => -2,
Denomination::NanoBitcoin => 1,
Denomination::PicoBitcoin => 4,
Denomination::Bit => -2,
Denomination::Satoshi => 0,
Denomination::MilliSatoshi => 3,
}
}
/// Returns stringly representation of this
fn as_str(self) -> &'static str {
match self {
Denomination::Bitcoin => "BTC",
Denomination::MilliBitcoin => "mBTC",
Denomination::MicroBitcoin => "uBTC",
Denomination::NanoBitcoin => "nBTC",
Denomination::PicoBitcoin => "pBTC",
Denomination::Bit => "bits",
Denomination::Satoshi => "satoshi",
Denomination::MilliSatoshi => "msat",
}
}
}
impl fmt::Display for Denomination {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for Denomination {
type Err = ParseAmountError;
/// Convert from a str to Denomination.
///
/// Any combination of upper and/or lower case, excluding uppercase of SI(m, u, n, p) is considered valid.
/// - Singular: BTC, mBTC, uBTC, nBTC, pBTC
/// - Plural or singular: sat, satoshi, bit, msat
///
/// Due to ambiguity between mega and milli, pico and peta we prohibit usage of leading capital 'M', 'P'.
fn from_str(s: &str) -> Result {
use self::ParseAmountError::*;
use self::Denomination as D;
let starts_with_uppercase = || s.starts_with(char::is_uppercase);
match denomination_from_str(s) {
None => Err(UnknownDenomination(s.to_owned())),
Some(D::MilliBitcoin) | Some(D::PicoBitcoin) | Some(D::MilliSatoshi) if starts_with_uppercase() => {
Err(PossiblyConfusingDenomination(s.to_owned()))
}
Some(D::NanoBitcoin) | Some(D::MicroBitcoin) if starts_with_uppercase() => {
Err(UnknownDenomination(s.to_owned()))
}
Some(d) => Ok(d),
}
}
}
fn denomination_from_str(mut s: &str) -> Option {
if s.eq_ignore_ascii_case("BTC") {
return Some(Denomination::Bitcoin);
}
if s.eq_ignore_ascii_case("mBTC") {
return Some(Denomination::MilliBitcoin);
}
if s.eq_ignore_ascii_case("uBTC") {
return Some(Denomination::MicroBitcoin);
}
if s.eq_ignore_ascii_case("nBTC") {
return Some(Denomination::NanoBitcoin);
}
if s.eq_ignore_ascii_case("pBTC") {
return Some(Denomination::PicoBitcoin);
}
if s.ends_with('s') || s.ends_with('S') {
s = &s[..(s.len() - 1)];
}
if s.eq_ignore_ascii_case("bit") {
return Some(Denomination::Bit);
}
if s.eq_ignore_ascii_case("satoshi") {
return Some(Denomination::Satoshi);
}
if s.eq_ignore_ascii_case("sat") {
return Some(Denomination::Satoshi);
}
if s.eq_ignore_ascii_case("msat") {
return Some(Denomination::MilliSatoshi);
}
None
}
/// An error during amount parsing.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseAmountError {
/// Amount is negative.
Negative,
/// Amount is too big to fit inside the type.
TooBig,
/// Amount has higher precision than supported by the type.
TooPrecise,
/// Invalid number format.
InvalidFormat,
/// Input string was too large.
InputTooLarge,
/// Invalid character in input.
InvalidCharacter(char),
/// The denomination was unknown.
UnknownDenomination(String),
/// The denomination has multiple possible interpretations.
PossiblyConfusingDenomination(String)
}
impl fmt::Display for ParseAmountError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ParseAmountError::Negative => f.write_str("amount is negative"),
ParseAmountError::TooBig => f.write_str("amount is too big"),
ParseAmountError::TooPrecise => f.write_str("amount has a too high precision"),
ParseAmountError::InvalidFormat => f.write_str("invalid number format"),
ParseAmountError::InputTooLarge => f.write_str("input string was too large"),
ParseAmountError::InvalidCharacter(c) => write!(f, "invalid character in input: {}", c),
ParseAmountError::UnknownDenomination(ref d) => write!(f, "unknown denomination: {}", d),
ParseAmountError::PossiblyConfusingDenomination(ref d) => {
let (letter, upper, lower) = match d.chars().next() {
Some('M') => ('M', "Mega", "milli"),
Some('P') => ('P', "Peta", "pico"),
// This panic could be avoided by adding enum ConfusingDenomination { Mega, Peta } but is it worth it?
_ => panic!("invalid error information"),
};
write!(f, "the '{}' at the beginning of {} should technically mean '{}' but that denomination is uncommon and maybe '{}' was intended", letter, d, upper, lower)
}
}
}
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl ::std::error::Error for ParseAmountError {}
fn is_too_precise(s: &str, precision: usize) -> bool {
s.contains('.') || precision >= s.len() || s.chars().rev().take(precision).any(|d| d != '0')
}
/// Parse decimal string in the given denomination into a satoshi value and a
/// bool indicator for a negative amount.
fn parse_signed_to_satoshi(
mut s: &str,
denom: Denomination,
) -> Result<(bool, u64), ParseAmountError> {
if s.is_empty() {
return Err(ParseAmountError::InvalidFormat);
}
if s.len() > 50 {
return Err(ParseAmountError::InputTooLarge);
}
let is_negative = s.starts_with('-');
if is_negative {
if s.len() == 1 {
return Err(ParseAmountError::InvalidFormat);
}
s = &s[1..];
}
let max_decimals = {
// The difference in precision between native (satoshi)
// and desired denomination.
let precision_diff = -denom.precision();
if precision_diff < 0 {
// If precision diff is negative, this means we are parsing
// into a less precise amount. That is not allowed unless
// there are no decimals and the last digits are zeroes as
// many as the difference in precision.
let last_n = unsigned_abs(precision_diff).into();
if is_too_precise(s, last_n) {
return Err(ParseAmountError::TooPrecise);
}
s = &s[0..s.len() - last_n];
0
} else {
precision_diff
}
};
let mut decimals = None;
let mut value: u64 = 0; // as satoshis
for c in s.chars() {
match c {
'0'..='9' => {
// Do `value = 10 * value + digit`, catching overflows.
match 10_u64.checked_mul(value) {
None => return Err(ParseAmountError::TooBig),
Some(val) => match val.checked_add((c as u8 - b'0') as u64) {
None => return Err(ParseAmountError::TooBig),
Some(val) => value = val,
},
}
// Increment the decimal digit counter if past decimal.
decimals = match decimals {
None => None,
Some(d) if d < max_decimals => Some(d + 1),
_ => return Err(ParseAmountError::TooPrecise),
};
}
'.' => match decimals {
None => decimals = Some(0),
// Double decimal dot.
_ => return Err(ParseAmountError::InvalidFormat),
},
c => return Err(ParseAmountError::InvalidCharacter(c)),
}
}
// Decimally shift left by `max_decimals - decimals`.
let scale_factor = max_decimals - decimals.unwrap_or(0);
for _ in 0..scale_factor {
value = match 10_u64.checked_mul(value) {
Some(v) => v,
None => return Err(ParseAmountError::TooBig),
};
}
Ok((is_negative, value))
}
/// Options given by `fmt::Formatter`
struct FormatOptions {
fill: char,
align: Option,
width: Option,
precision: Option,
sign_plus: bool,
sign_aware_zero_pad: bool,
}
impl FormatOptions {
fn from_formatter(f: &fmt::Formatter) -> Self {
FormatOptions {
fill: f.fill(),
align: f.align(),
width: f.width(),
precision: f.precision(),
sign_plus: f.sign_plus(),
sign_aware_zero_pad: f.sign_aware_zero_pad(),
}
}
}
impl Default for FormatOptions {
fn default() -> Self {
FormatOptions {
fill: ' ',
align: None,
width: None,
precision: None,
sign_plus: false,
sign_aware_zero_pad: false,
}
}
}
fn dec_width(mut num: u64) -> usize {
let mut width = 1;
loop {
num /= 10;
if num == 0 {
break;
}
width += 1;
}
width
}
// NIH due to MSRV, impl copied from `core`
fn unsigned_abs(x: i8) -> u8 {
x.wrapping_abs() as u8
}
fn repeat_char(f: &mut dyn fmt::Write, c: char, count: usize) -> fmt::Result {
for _ in 0..count {
f.write_char(c)?;
}
Ok(())
}
/// Format the given satoshi amount in the given denomination.
fn fmt_satoshi_in(
satoshi: u64,
negative: bool,
f: &mut dyn fmt::Write,
denom: Denomination,
show_denom: bool,
options: FormatOptions,
) -> fmt::Result {
let precision = denom.precision();
// First we normalize the number:
// {num_before_decimal_point}{:0exp}{"." if nb_decimals > 0}{:0nb_decimals}{num_after_decimal_point}{:0trailing_decimal_zeros}
let mut num_after_decimal_point = 0;
let mut norm_nb_decimals = 0;
let mut num_before_decimal_point = satoshi;
let trailing_decimal_zeros;
let mut exp = 0;
match precision.cmp(&0) {
// We add the number of zeroes to the end
Ordering::Greater => {
if satoshi > 0 {
exp = precision as usize;
}
trailing_decimal_zeros = options.precision.unwrap_or(0);
},
Ordering::Less => {
let precision = unsigned_abs(precision);
let divisor = 10u64.pow(precision.into());
num_before_decimal_point = satoshi / divisor;
num_after_decimal_point = satoshi % divisor;
// normalize by stripping trailing zeros
if num_after_decimal_point == 0 {
norm_nb_decimals = 0;
} else {
norm_nb_decimals = usize::from(precision);
while num_after_decimal_point % 10 == 0 {
norm_nb_decimals -= 1;
num_after_decimal_point /= 10
}
}
// compute requested precision
let opt_precision = options.precision.unwrap_or(0);
trailing_decimal_zeros = opt_precision.saturating_sub(norm_nb_decimals);
},
Ordering::Equal => trailing_decimal_zeros = options.precision.unwrap_or(0),
}
let total_decimals = norm_nb_decimals + trailing_decimal_zeros;
// Compute expected width of the number
let mut num_width = if total_decimals > 0 {
// 1 for decimal point
1 + total_decimals
} else {
0
};
num_width += dec_width(num_before_decimal_point) + exp;
if options.sign_plus || negative {
num_width += 1;
}
if show_denom {
// + 1 for space
num_width += denom.as_str().len() + 1;
}
let width = options.width.unwrap_or(0);
let (left_pad, pad_right) = match (num_width < width, options.sign_aware_zero_pad, options.align.unwrap_or(fmt::Alignment::Right)) {
(false, _, _) => (0, 0),
// Alignment is always right (ignored) when zero-padding
(true, true, _) | (true, false, fmt::Alignment::Right) => (width - num_width, 0),
(true, false, fmt::Alignment::Left) => (0, width - num_width),
// If the required padding is odd it needs to be skewed to the left
(true, false, fmt::Alignment::Center) => ((width - num_width) / 2, (width - num_width + 1) / 2),
};
if !options.sign_aware_zero_pad {
repeat_char(f, options.fill, left_pad)?;
}
if negative {
write!(f, "-")?;
} else if options.sign_plus {
write!(f, "+")?;
}
if options.sign_aware_zero_pad {
repeat_char(f, '0', left_pad)?;
}
write!(f, "{}", num_before_decimal_point)?;
repeat_char(f, '0', exp)?;
if total_decimals > 0 {
write!(f, ".")?;
}
if norm_nb_decimals > 0 {
write!(f, "{:0width$}", num_after_decimal_point, width = norm_nb_decimals)?;
}
repeat_char(f, '0', trailing_decimal_zeros)?;
if show_denom {
write!(f, " {}", denom.as_str())?;
}
repeat_char(f, options.fill, pad_right)?;
Ok(())
}
/// Amount
///
/// The [Amount] type can be used to express Bitcoin amounts that supports
/// 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. Also note that
/// since the internal representation of amounts is unsigned, subtracting below
/// zero is considered an underflow and will cause a panic if you're not using
/// the checked arithmetic methods.
///
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Amount(u64);
impl Amount {
/// The zero amount.
pub const ZERO: Amount = Amount(0);
/// Exactly one satoshi.
pub const ONE_SAT: Amount = Amount(1);
/// Exactly one bitcoin.
pub const ONE_BTC: Amount = Amount(100_000_000);
/// The maximum value allowed as an amount. Useful for sanity checking.
pub const MAX_MONEY: Amount = Amount(21_000_000 * 100_000_000);
/// Create an [Amount] with satoshi precision and the given number of satoshis.
pub const fn from_sat(satoshi: u64) -> Amount {
Amount(satoshi)
}
/// Get the number of satoshis in this [Amount].
pub fn as_sat(self) -> u64 {
self.0
}
/// The maximum value of an [Amount].
pub fn max_value() -> Amount {
Amount(u64::max_value())
}
/// The minimum value of an [Amount].
pub fn min_value() -> Amount {
Amount(u64::min_value())
}
/// Convert from a value expressing bitcoins to an [Amount].
pub fn from_btc(btc: f64) -> Result {
Amount::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 {
let (negative, satoshi) = parse_signed_to_satoshi(s, denom)?;
if negative {
return Err(ParseAmountError::Negative);
}
if satoshi > i64::max_value() as u64 {
return Err(ParseAmountError::TooBig);
}
Ok(Amount::from_sat(satoshi))
}
/// 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 {
let mut split = s.splitn(3, ' ');
let amt_str = split.next().unwrap();
let denom_str = split.next().ok_or(ParseAmountError::InvalidFormat)?;
if split.next().is_some() {
return Err(ParseAmountError::InvalidFormat);
}
Amount::from_str_in(amt_str, denom_str.parse()?)
}
/// Express this [Amount] as a floating-point value in the given denomination.
///
/// Please be aware of the risk of using floating-point numbers.
pub fn to_float_in(self, denom: Denomination) -> f64 {
f64::from_str(&self.to_string_in(denom)).unwrap()
}
/// Express this [Amount] 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.
pub fn as_btc(self) -> f64 {
self.to_float_in(Denomination::Bitcoin)
}
/// Convert this [Amount] in floating-point notation with a given
/// denomination.
/// Can return error if the amount is too big, too precise or negative.
///
/// Please be aware of the risk of using floating-point numbers.
pub fn from_float_in(value: f64, denom: Denomination) -> Result {
if value < 0.0 {
return Err(ParseAmountError::Negative);
}
// 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.
Amount::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.as_sat(),
is_negative: false,
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.as_sat(),
is_negative: false,
style: DisplayStyle::DynamicDenomination,
}
}
/// Format the value of this [Amount] in the given denomination.
///
/// Does not include the denomination.
pub fn fmt_value_in(self, f: &mut dyn fmt::Write, denom: Denomination) -> fmt::Result {
fmt_satoshi_in(self.as_sat(), false, f, denom, false, FormatOptions::default())
}
/// Get a string number of this [Amount] in the given denomination.
///
/// Does not include the denomination.
pub fn to_string_in(self, denom: Denomination) -> String {
let mut buf = String::new();
self.fmt_value_in(&mut buf, denom).unwrap();
buf
}
/// Get a formatted string of this [Amount] in the given denomination,
/// suffixed with the abbreviation for the denomination.
pub fn to_string_with_denomination(self, denom: Denomination) -> String {
let mut buf = String::new();
self.fmt_value_in(&mut buf, denom).unwrap();
write!(buf, " {}", denom).unwrap();
buf
}
// Some arithmetic that doesn't fit in `core::ops` traits.
/// Checked addition.
/// Returns [None] if overflow occurred.
pub fn checked_add(self, rhs: Amount) -> Option {
self.0.checked_add(rhs.0).map(Amount)
}
/// Checked subtraction.
/// Returns [None] if overflow occurred.
pub fn checked_sub(self, rhs: Amount) -> Option {
self.0.checked_sub(rhs.0).map(Amount)
}
/// Checked multiplication.
/// Returns [None] if overflow occurred.
pub fn checked_mul(self, rhs: u64) -> Option {
self.0.checked_mul(rhs).map(Amount)
}
/// 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: u64) -> Option {
self.0.checked_div(rhs).map(Amount)
}
/// Checked remainder.
/// Returns [None] if overflow occurred.
pub fn checked_rem(self, rhs: u64) -> Option {
self.0.checked_rem(rhs).map(Amount)
}
/// Convert to a signed amount.
pub fn to_signed(self) -> Result {
if self.as_sat() > SignedAmount::max_value().as_sat() as u64 {
Err(ParseAmountError::TooBig)
} else {
Ok(SignedAmount::from_sat(self.as_sat() as i64))
}
}
}
impl default::Default for Amount {
fn default() -> Self {
Amount::ZERO
}
}
impl fmt::Debug for Amount {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Amount({:.8} BTC)", self.as_btc())
}
}
// No one should depend on a binding contract for Display for this type.
// Just using Bitcoin denominated string.
impl fmt::Display for Amount {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.fmt_value_in(f, Denomination::Bitcoin)?;
write!(f, " {}", Denomination::Bitcoin)
}
}
impl ops::Add for Amount {
type Output = Amount;
fn add(self, rhs: Amount) -> Self::Output {
self.checked_add(rhs).expect("Amount addition error")
}
}
impl ops::AddAssign for Amount {
fn add_assign(&mut self, other: Amount) {
*self = *self + other
}
}
impl ops::Sub for Amount {
type Output = Amount;
fn sub(self, rhs: Amount) -> Self::Output {
self.checked_sub(rhs).expect("Amount subtraction error")
}
}
impl ops::SubAssign for Amount {
fn sub_assign(&mut self, other: Amount) {
*self = *self - other
}
}
impl ops::Rem for Amount {
type Output = Amount;
fn rem(self, modulus: u64) -> Self {
self.checked_rem(modulus).expect("Amount remainder error")
}
}
impl ops::RemAssign for Amount {
fn rem_assign(&mut self, modulus: u64) {
*self = *self % modulus
}
}
impl ops::Mul for Amount {
type Output = Amount;
fn mul(self, rhs: u64) -> Self::Output {
self.checked_mul(rhs).expect("Amount multiplication error")
}
}
impl ops::MulAssign for Amount {
fn mul_assign(&mut self, rhs: u64) {
*self = *self * rhs
}
}
impl ops::Div for Amount {
type Output = Amount;
fn div(self, rhs: u64) -> Self::Output {
self.checked_div(rhs).expect("Amount division error")
}
}
impl ops::DivAssign for Amount {
fn div_assign(&mut self, rhs: u64) {
*self = *self / rhs
}
}
impl FromStr for Amount {
type Err = ParseAmountError;
fn from_str(s: &str) -> Result {
Amount::from_str_with_denomination(s)
}
}
impl ::core::iter::Sum for Amount {
fn sum>(iter: I) -> Self {
let sats: u64 = iter.map(|amt| amt.0).sum();
Amount::from_sat(sats)
}
}
/// A helper/builder that displays amount with specified settings.
///
/// This provides richer interface than `fmt::Formatter`:
///
/// * Ability to select denomination
/// * Show or hide denomination
/// * Dynamically-selected denomination - show in sats if less than 1 BTC.
///
/// However this can still be combined with `fmt::Formatter` options to precisely control zeros,
/// padding, alignment... The formatting works like floats from `core` but note that precision will
/// **never** be lossy - that means no rounding.
///
/// See [`Amount::display_in`] and [`Amount::display_dynamic`] on how to construct this.
#[derive(Debug, Clone)]
pub struct Display {
/// Absolute value of satoshis to display (sign is below)
sats_abs: u64,
/// The sign
is_negative: bool,
/// How to display the value
style: DisplayStyle,
}
impl Display {
/// Makes subsequent calls to `Display::fmt` display denomination.
pub fn show_denomination(mut self) -> Self {
match &mut self.style {
DisplayStyle::FixedDenomination { show_denomination, .. } => *show_denomination = true,
// No-op because dynamic denomination is always shown
DisplayStyle::DynamicDenomination => (),
}
self
}
}
impl fmt::Display for Display {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let format_options = FormatOptions::from_formatter(f);
match &self.style {
DisplayStyle::FixedDenomination { show_denomination, denomination } => fmt_satoshi_in(self.sats_abs, self.is_negative, f, *denomination, *show_denomination, format_options),
DisplayStyle::DynamicDenomination if self.sats_abs >= Amount::ONE_BTC.as_sat() => {
fmt_satoshi_in(self.sats_abs, self.is_negative, f, Denomination::Bitcoin, true, format_options)
},
DisplayStyle::DynamicDenomination => {
fmt_satoshi_in(self.sats_abs, self.is_negative, f, Denomination::Satoshi, true, format_options)
},
}
}
}
#[derive(Clone, Debug)]
enum DisplayStyle {
FixedDenomination { denomination: Denomination, show_denomination: bool, },
DynamicDenomination,
}
/// SignedAmount
///
/// The [SignedAmount] type can be used to express Bitcoin amounts that supports
/// 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);
/// Create an [SignedAmount] with satoshi precision and the given number of satoshis.
pub const fn from_sat(satoshi: i64) -> SignedAmount {
SignedAmount(satoshi)
}
/// Get the number of satoshis in this [SignedAmount].
pub fn as_sat(self) -> i64 {
self.0
}
/// The maximum value of an [SignedAmount].
pub fn max_value() -> SignedAmount {
SignedAmount(i64::max_value())
}
/// The minimum value of an [SignedAmount].
pub fn min_value() -> SignedAmount {
SignedAmount(i64::min_value())
}
/// Convert from a value expressing bitcoins to an [SignedAmount].
pub fn from_btc(btc: f64) -> Result {
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 {
let (negative, satoshi) = parse_signed_to_satoshi(s, denom)?;
if satoshi > i64::max_value() as u64 {
return Err(ParseAmountError::TooBig);
}
Ok(match negative {
true => SignedAmount(-(satoshi as i64)),
false => SignedAmount(satoshi 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 {
let mut split = s.splitn(3, ' ');
let amt_str = split.next().unwrap();
let denom_str = split.next().ok_or(ParseAmountError::InvalidFormat)?;
if split.next().is_some() {
return Err(ParseAmountError::InvalidFormat);
}
SignedAmount::from_str_in(amt_str, denom_str.parse()?)
}
/// Express this [SignedAmount] as a floating-point value in the given denomination.
///
/// Please be aware of the risk of using floating-point numbers.
pub fn to_float_in(self, denom: Denomination) -> f64 {
f64::from_str(&self.to_string_in(denom)).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.
pub fn as_btc(self) -> f64 {
self.to_float_in(Denomination::Bitcoin)
}
/// Convert this [SignedAmount] in floating-point notation with a given
/// denomination.
/// Can return error if the amount is too big, too precise or negative.
///
/// Please be aware of the risk of using floating-point numbers.
pub fn from_float_in(
value: f64,
denom: Denomination,
) -> Result {
// 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)
}
/// Returns the absolute value as satoshis.
///
/// This is the implementation of `unsigned_abs()` copied from `core` to support older MSRV.
fn to_sat_abs(self) -> u64 {
self.as_sat().wrapping_abs() as u64
}
/// Create an object that implements [`fmt::Display`] using specified denomination.
pub fn display_in(self, denomination: Denomination) -> Display {
Display {
sats_abs: self.to_sat_abs(),
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.to_sat_abs(),
is_negative: self.is_negative(),
style: DisplayStyle::DynamicDenomination,
}
}
/// Format the value of this [SignedAmount] in the given denomination.
///
/// Does not include the denomination.
pub fn fmt_value_in(self, f: &mut dyn fmt::Write, denom: Denomination) -> fmt::Result {
fmt_satoshi_in(self.to_sat_abs(), self.is_negative(), f, denom, false, FormatOptions::default())
}
/// Get a string number of this [SignedAmount] in the given denomination.
///
/// Does not include the denomination.
pub fn to_string_in(self, denom: Denomination) -> String {
let mut buf = String::new();
self.fmt_value_in(&mut buf, denom).unwrap();
buf
}
/// Get a formatted string of this [SignedAmount] in the given denomination,
/// suffixed with the abbreviation for the denomination.
pub fn to_string_with_denomination(self, denom: Denomination) -> String {
let mut buf = String::new();
self.fmt_value_in(&mut buf, denom).unwrap();
write!(buf, " {}", denom).unwrap();
buf
}
// 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())
}
/// 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()
}
/// 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()
}
/// 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()
}
/// Get the absolute value of this [SignedAmount].
/// Returns [None] if overflow occurred. (`self == min_value()`)
pub fn checked_abs(self) -> Option {
self.0.checked_abs().map(SignedAmount)
}
/// Checked addition.
/// Returns [None] if overflow occurred.
pub fn checked_add(self, rhs: SignedAmount) -> Option {
self.0.checked_add(rhs.0).map(SignedAmount)
}
/// Checked subtraction.
/// Returns [None] if overflow occurred.
pub fn checked_sub(self, rhs: SignedAmount) -> Option {
self.0.checked_sub(rhs.0).map(SignedAmount)
}
/// Checked multiplication.
/// Returns [None] if overflow occurred.
pub fn checked_mul(self, rhs: i64) -> Option {
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 {
self.0.checked_div(rhs).map(SignedAmount)
}
/// Checked remainder.
/// Returns [None] if overflow occurred.
pub fn checked_rem(self, rhs: i64) -> Option {
self.0.checked_rem(rhs).map(SignedAmount)
}
/// 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 {
if self.is_negative() || rhs.is_negative() || rhs > self {
None
} else {
self.checked_sub(rhs)
}
}
/// Convert to an unsigned amount.
pub fn to_unsigned(self) -> Result {
if self.is_negative() {
Err(ParseAmountError::Negative)
} else {
Ok(Amount::from_sat(self.as_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({:.8} BTC)", self.as_btc())
}
}
// 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 {
self.fmt_value_in(f, Denomination::Bitcoin)?;
write!(f, " {}", Denomination::Bitcoin)
}
}
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 for SignedAmount {
type Output = SignedAmount;
fn rem(self, modulus: i64) -> Self {
self.checked_rem(modulus).expect("SignedAmount remainder error")
}
}
impl ops::RemAssign for SignedAmount {
fn rem_assign(&mut self, modulus: i64) {
*self = *self % modulus
}
}
impl ops::Mul for SignedAmount {
type Output = SignedAmount;
fn mul(self, rhs: i64) -> Self::Output {
self.checked_mul(rhs).expect("SignedAmount multiplication error")
}
}
impl ops::MulAssign for SignedAmount {
fn mul_assign(&mut self, rhs: i64) {
*self = *self * rhs
}
}
impl ops::Div for SignedAmount {
type Output = SignedAmount;
fn div(self, rhs: i64) -> Self::Output {
self.checked_div(rhs).expect("SignedAmount division error")
}
}
impl ops::DivAssign for SignedAmount {
fn div_assign(&mut self, rhs: i64) {
*self = *self / rhs
}
}
impl FromStr for SignedAmount {
type Err = ParseAmountError;
fn from_str(s: &str) -> Result {
SignedAmount::from_str_with_denomination(s)
}
}
impl ::core::iter::Sum for SignedAmount {
fn sum>(iter: I) -> Self {
let sats: i64 = iter.map(|amt| amt.0).sum();
SignedAmount::from_sat(sats)
}
}
/// Calculate the sum over the iterator using checked arithmetic.
pub trait CheckedSum: private::SumSeal {
/// Calculate the sum over the iterator using checked arithmetic. If an over or underflow would
/// happen it returns `None`.
fn checked_sum(self) -> Option;
}
impl CheckedSum for T where T: Iterator {
fn checked_sum(mut self) -> Option {
let first = Some(self.next().unwrap_or_default());
self.fold(
first,
|acc, item| acc.and_then(|acc| acc.checked_add(item))
)
}
}
impl CheckedSum for T where T: Iterator {
fn checked_sum(mut self) -> Option {
let first = Some(self.next().unwrap_or_default());
self.fold(first, |acc, item| acc.and_then(|acc| acc.checked_add(item)))
}
}
mod private {
use crate::{Amount, SignedAmount};
/// Used to seal the `CheckedSum` trait
pub trait SumSeal {}
impl SumSeal for T where T: Iterator {}
impl SumSeal for T where T: Iterator {}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub mod serde {
// methods are implementation of a standardized serde-specific signature
#![allow(missing_docs)]
//! This module adds serde serialization and deserialization support for Amounts.
//! Since there is not a default way to serialize and deserialize Amounts, multiple
//! ways are supported and it's up to the user to decide which serialiation to use.
//! The provided modules can be used as follows:
//!
//! ```rust,ignore
//! use serde::{Serialize, Deserialize};
//! use bitcoin::Amount;
//!
//! #[derive(Serialize, Deserialize)]
//! pub struct HasAmount {
//! #[serde(with = "bitcoin::util::amount::serde::as_btc")]
//! pub amount: Amount,
//! }
//! ```
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::util::amount::{Amount, Denomination, SignedAmount};
/// This trait is used only to avoid code duplication and naming collisions
/// of the different serde serialization crates.
///
/// TODO: Add the private::Sealed bound in next breaking release
pub trait SerdeAmount: Copy + Sized {
fn ser_sat(self, s: S) -> Result;
fn des_sat<'d, D: Deserializer<'d>>(d: D) -> Result;
fn ser_btc(self, s: S) -> Result;
fn des_btc<'d, D: Deserializer<'d>>(d: D) -> Result;
}
mod private {
/// add this as a trait bound to traits which consumers of this library
/// should not be able to implement.
pub trait Sealed {}
impl Sealed for super::Amount {}
impl Sealed for super::SignedAmount {}
}
/// This trait is only for internal Amount type serialization/deserialization
pub trait SerdeAmountForOpt: Copy + Sized + SerdeAmount + private::Sealed {
fn type_prefix() -> &'static str;
fn ser_sat_opt(self, s: S) -> Result;
fn ser_btc_opt(self, s: S) -> Result;
}
impl SerdeAmount for Amount {
fn ser_sat(self, s: S) -> Result {
u64::serialize(&self.as_sat(), s)
}
fn des_sat<'d, D: Deserializer<'d>>(d: D) -> Result {
Ok(Amount::from_sat(u64::deserialize(d)?))
}
fn ser_btc(self, s: S) -> Result {
f64::serialize(&self.to_float_in(Denomination::Bitcoin), s)
}
fn des_btc<'d, D: Deserializer<'d>>(d: D) -> Result {
use serde::de::Error;
Ok(Amount::from_btc(f64::deserialize(d)?).map_err(D::Error::custom)?)
}
}
impl SerdeAmountForOpt for Amount {
fn type_prefix() -> &'static str {
"u"
}
fn ser_sat_opt(self, s: S) -> Result {
s.serialize_some(&self.as_sat())
}
fn ser_btc_opt(self, s: S) -> Result {
s.serialize_some(&self.as_btc())
}
}
impl SerdeAmount for SignedAmount {
fn ser_sat(self, s: S) -> Result {
i64::serialize(&self.as_sat(), s)
}
fn des_sat<'d, D: Deserializer<'d>>(d: D) -> Result {
Ok(SignedAmount::from_sat(i64::deserialize(d)?))
}
fn ser_btc(self, s: S) -> Result {
f64::serialize(&self.to_float_in(Denomination::Bitcoin), s)
}
fn des_btc<'d, D: Deserializer<'d>>(d: D) -> Result {
use serde::de::Error;
Ok(SignedAmount::from_btc(f64::deserialize(d)?).map_err(D::Error::custom)?)
}
}
impl SerdeAmountForOpt for SignedAmount {
fn type_prefix() -> &'static str {
"i"
}
fn ser_sat_opt(self, s: S) -> Result {
s.serialize_some(&self.as_sat())
}
fn ser_btc_opt(self, s: S) -> Result {
s.serialize_some(&self.as_btc())
}
}
pub mod as_sat {
//! Serialize and deserialize [`Amount`](crate::Amount) as real numbers denominated in satoshi.
//! Use with `#[serde(with = "amount::serde::as_sat")]`.
use serde::{Deserializer, Serializer};
use crate::util::amount::serde::SerdeAmount;
pub fn serialize(a: &A, s: S) -> Result {
a.ser_sat(s)
}
pub fn deserialize<'d, A: SerdeAmount, D: Deserializer<'d>>(d: D) -> Result {
A::des_sat(d)
}
pub mod opt {
//! Serialize and deserialize [`Option`](crate::Amount) as real numbers denominated in satoshi.
//! Use with `#[serde(default, with = "amount::serde::as_sat::opt")]`.
use serde::{Deserializer, Serializer, de};
use crate::util::amount::serde::SerdeAmountForOpt;
use core::fmt;
use core::marker::PhantomData;
pub fn serialize(
a: &Option,
s: S,
) -> Result {
match *a {
Some(a) => a.ser_sat_opt(s),
None => s.serialize_none(),
}
}
pub fn deserialize<'d, A: SerdeAmountForOpt, D: Deserializer<'d>>(
d: D,
) -> Result