bitcoin_amount/
lib.rs

1#![warn(missing_docs)]
2
3//! # Bitcoin Amount
4//!
5
6#[cfg(feature = "serde")]
7extern crate serde;
8#[cfg(feature = "serde_json")]
9extern crate serde_json;
10#[cfg(feature = "strason")]
11extern crate strason;
12
13use std::error;
14use std::fmt::{self, Display, Formatter};
15
16use std::ops::{Add, Div, Mul, Sub};
17
18use std::num::ParseFloatError;
19use std::str::FromStr;
20
21/// The primitive type that holds the satoshis.
22type Inner = i64;
23
24/// The amount of satoshis in a BTC.
25pub const SAT_PER_BTC: i64 = 100_000_000;
26
27/// The amount of satoshis in a BTC (floating point).
28pub const SAT_PER_BTC_FP: f64 = 100_000_000.0;
29
30/// Maximum value in an `Amount`.
31pub const MAX: Amount = Amount(Inner::max_value());
32/// Minimum value in an `Amount`.
33pub const MIN: Amount = Amount(Inner::min_value());
34
35/// A bitcoin amount integer type.
36#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
37pub struct Amount(Inner);
38
39impl Amount {
40    /// Creates an `Amount` from the given type.
41    pub fn from_btc<T>(btc: T) -> Amount
42    where T:
43          IntoBtc,
44    {
45        btc.into_btc()
46    }
47
48    /// Creates a new `Amount` from a satoshi amount.
49    pub fn from_sat(sat: Inner) -> Amount {
50        Amount(sat)
51    }
52
53    /// Returns the additive identity of `Amount`.
54    pub fn zero() -> Amount {
55        Amount(0)
56    }
57
58    /// Returns the multiplicative identity of `Amount`.
59    pub fn one() -> Amount {
60        Amount(1)
61    }
62
63    /// Maximum value that can fit in an `Amount`.
64    pub fn max_value() -> Amount { MAX }
65
66    /// Minimum value that can fit in an `Amount`.
67    pub fn min_value() -> Amount { MIN }
68
69    /// Converts this `Amount` to the inner satoshis.
70    pub fn into_inner(self) -> Inner {
71        self.0
72    }
73}
74
75impl Add for Amount {
76    type Output = Amount;
77    
78    fn add(self, rhs: Amount) -> Self::Output {
79        Amount::from_sat(self.0 + rhs.0)
80    }
81}
82
83impl Div for Amount {
84    type Output = Amount;
85    
86    fn div(self, rhs: Amount) -> Self::Output {
87        Amount::from_sat(self.0 / rhs.0)
88    }
89}
90
91impl Mul for Amount {
92    type Output = Amount;
93    
94    fn mul(self, rhs: Amount) -> Self::Output {
95        Amount::from_sat(self.0 * rhs.0)
96    }
97}
98
99impl Sub for Amount {
100    type Output = Amount;
101    
102    fn sub(self, rhs: Amount) -> Self::Output {
103        Amount::from_sat(self.0 - rhs.0)
104    }
105}
106
107#[cfg(feature = "serde")]
108impl<'de> serde::Deserialize<'de> for Amount {
109    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
110    where
111        D: serde::de::Deserializer<'de>
112    {
113        Inner::deserialize(deserializer).map(Amount)
114    }
115}
116
117#[cfg(feature = "serde")]
118impl serde::Serialize for Amount {
119    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
120    where
121        S: serde::ser::Serializer
122    {
123        Inner::serialize(&self.0, serializer)
124    }
125}
126
127impl FromStr for Amount {
128    type Err = ParseAmountError;
129
130    fn from_str(s: &str) -> Result<Self, Self::Err> {
131        let btc = f64::from_str(s).map_err(ParseAmountError)?;
132
133        Ok(Amount::from_btc(btc))
134    }
135}
136
137/// An error during `Amount` parsing.
138#[derive(Debug)]
139pub struct ParseAmountError(ParseFloatError);
140
141impl Display for ParseAmountError {
142    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
143        write!(fmt, "invalid floating point integer: {}", self.0)
144    }
145}
146
147impl error::Error for ParseAmountError {
148    fn cause(&self) -> Option<&error::Error> {
149        Some(&self.0)
150    }
151
152    fn description(&self) -> &'static str {
153        "floating point error"
154    }
155}
156
157fn round_and_to_sat(v: f64) -> Inner {
158    if v < 0.0 {
159        ((v * SAT_PER_BTC_FP) - 0.5) as Inner
160    } else {
161        ((v * SAT_PER_BTC_FP) + 0.5) as Inner
162    }
163}
164
165/// Trait to mark types as convertable into `Amount`s.
166///
167/// Types that implement this trait should perform the conversion from BTC
168/// amounts to satoshis e.g. an f64 performs the conversion of "0.00000025" to
169/// 25 satoshis. See `Amount::from_sat`.
170pub trait IntoBtc {
171    /// Performs the conversion.
172    fn into_btc(self) -> Amount;
173}
174
175impl<'a> IntoBtc for &'a f64 {
176    fn into_btc(self) -> Amount {
177        let sat = round_and_to_sat(*self);
178        Amount::from_sat(sat)
179    }
180}
181
182impl IntoBtc for f64 {
183    fn into_btc(self) -> Amount {
184        let sat = round_and_to_sat(self);
185        Amount::from_sat(sat)
186    }
187}
188
189#[cfg(feature = "serde_json")]
190impl<'a> IntoBtc for &'a serde_json::value::Number {
191    fn into_btc(self) -> Amount {
192        let num = format!("{}", self);
193        Amount::from_str(&*num).unwrap()
194    }
195}
196
197#[cfg(feature = "serde_json")]
198impl IntoBtc for serde_json::value::Number {
199    fn into_btc(self) -> Amount {
200        let num = format!("{}", self);
201        Amount::from_str(&*num).unwrap()
202    }
203}
204
205#[cfg(feature = "strason")]
206impl<'a> IntoBtc for &'a strason::Json {
207    fn into_btc(self) -> Amount {
208        Amount::from_str(self.num().unwrap()).unwrap()
209    }
210}
211
212#[cfg(feature = "strason")]
213impl IntoBtc for  strason::Json {
214    fn into_btc(self) -> Amount {
215        Amount::from_str(self.num().unwrap()).unwrap()
216    }
217}
218
219#[cfg(test)]
220pub mod tests {
221    use std::str::FromStr;
222
223    use super::*;
224
225    #[test]
226    fn amount_from_btc() {
227        assert_eq!(Amount::from_btc(0.00253583).0, 253583);
228    }
229
230    #[test]
231    fn amount_from_sat() {
232        assert_eq!(Amount::from_sat(253583).0, 253583);
233    }
234
235    #[test]
236    fn amount_from_str() {
237        let amt = Amount::from_str("0.00253583").unwrap();
238        assert_eq!(amt, Amount::from_sat(253583));
239        let amt = Amount::from_str("0.10000000").unwrap();
240        assert_eq!(amt, Amount::from_sat(10_000_000));
241    }
242
243    #[test]
244    fn amount_add_div_mul_sub() {
245        let res = ((Amount::from_btc(0.0025) +
246                    Amount::from_btc(0.0005)) * (Amount::from_btc(2.0))) /
247                    Amount::from_btc(2.0);
248
249        assert_eq!(res, Amount::from_btc(0.003));
250    }
251}