#![warn(missing_docs)]
#[cfg(feature = "serde")]
extern crate serde;
#[cfg(feature = "serde_json")]
extern crate serde_json;
#[cfg(feature = "strason")]
extern crate strason;
use std::error;
use std::fmt::{self, Display, Formatter};
use std::ops::{Add, Div, Mul, Sub};
use std::num::ParseFloatError;
use std::str::FromStr;
type Inner = i64;
pub const SAT_PER_BTC: i64 = 100_000_000;
pub const SAT_PER_BTC_FP: f64 = 100_000_000.0;
pub const MAX: Amount = Amount(Inner::max_value());
pub const MIN: Amount = Amount(Inner::min_value());
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
pub struct Amount(Inner);
impl Amount {
pub fn from_btc<T>(btc: T) -> Amount
where T:
IntoBtc,
{
btc.into_btc()
}
pub fn from_sat(sat: Inner) -> Amount {
Amount(sat)
}
pub fn zero() -> Amount {
Amount(0)
}
pub fn one() -> Amount {
Amount(1)
}
pub fn max_value() -> Amount { MAX }
pub fn min_value() -> Amount { MIN }
pub fn into_inner(self) -> Inner {
self.0
}
}
impl Add for Amount {
type Output = Amount;
fn add(self, rhs: Amount) -> Self::Output {
Amount::from_sat(self.0 + rhs.0)
}
}
impl Div for Amount {
type Output = Amount;
fn div(self, rhs: Amount) -> Self::Output {
Amount::from_sat(self.0 / rhs.0)
}
}
impl Mul for Amount {
type Output = Amount;
fn mul(self, rhs: Amount) -> Self::Output {
Amount::from_sat(self.0 * rhs.0)
}
}
impl Sub for Amount {
type Output = Amount;
fn sub(self, rhs: Amount) -> Self::Output {
Amount::from_sat(self.0 - rhs.0)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Amount {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>
{
Inner::deserialize(deserializer).map(Amount)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Amount {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer
{
Inner::serialize(&self.0, serializer)
}
}
impl FromStr for Amount {
type Err = ParseAmountError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let btc = f64::from_str(s).map_err(ParseAmountError)?;
Ok(Amount::from_btc(btc))
}
}
#[derive(Debug)]
pub struct ParseAmountError(ParseFloatError);
impl Display for ParseAmountError {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
write!(fmt, "invalid floating point integer: {}", self.0)
}
}
impl error::Error for ParseAmountError {
fn cause(&self) -> Option<&error::Error> {
Some(&self.0)
}
fn description(&self) -> &'static str {
"floating point error"
}
}
fn round_and_to_sat(v: f64) -> Inner {
if v < 0.0 {
((v * SAT_PER_BTC_FP) - 0.5) as Inner
} else {
((v * SAT_PER_BTC_FP) + 0.5) as Inner
}
}
pub trait IntoBtc {
fn into_btc(self) -> Amount;
}
impl<'a> IntoBtc for &'a f64 {
fn into_btc(self) -> Amount {
let sat = round_and_to_sat(*self);
Amount::from_sat(sat)
}
}
impl IntoBtc for f64 {
fn into_btc(self) -> Amount {
let sat = round_and_to_sat(self);
Amount::from_sat(sat)
}
}
#[cfg(feature = "serde_json")]
impl<'a> IntoBtc for &'a serde_json::value::Number {
fn into_btc(self) -> Amount {
let num = format!("{}", self);
Amount::from_str(&*num).unwrap()
}
}
#[cfg(feature = "serde_json")]
impl IntoBtc for serde_json::value::Number {
fn into_btc(self) -> Amount {
let num = format!("{}", self);
Amount::from_str(&*num).unwrap()
}
}
#[cfg(feature = "strason")]
impl<'a> IntoBtc for &'a strason::Json {
fn into_btc(self) -> Amount {
Amount::from_str(self.num().unwrap()).unwrap()
}
}
#[cfg(feature = "strason")]
impl IntoBtc for strason::Json {
fn into_btc(self) -> Amount {
Amount::from_str(self.num().unwrap()).unwrap()
}
}
#[cfg(test)]
pub mod tests {
use std::str::FromStr;
use super::*;
#[test]
fn amount_from_btc() {
assert_eq!(Amount::from_btc(0.00253583).0, 253583);
}
#[test]
fn amount_from_sat() {
assert_eq!(Amount::from_sat(253583).0, 253583);
}
#[test]
fn amount_from_str() {
let amt = Amount::from_str("0.00253583").unwrap();
assert_eq!(amt, Amount::from_sat(253583));
let amt = Amount::from_str("0.10000000").unwrap();
assert_eq!(amt, Amount::from_sat(10_000_000));
}
#[test]
fn amount_add_div_mul_sub() {
let res = ((Amount::from_btc(0.0025) +
Amount::from_btc(0.0005)) * (Amount::from_btc(2.0))) /
Amount::from_btc(2.0);
assert_eq!(res, Amount::from_btc(0.003));
}
}