1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//! Primitive fixed-point decimal types.
//!
//! It's necessary to represent decimals accurately in some scenarios,
//! such as financial field. Primitive floating-point types (`f32`
//! and `f64`) can not accurately represent decimal fractions because
//! they use binary to represent values. Here we use integer types to
//! represent values, and handle fractions in base 10.
//!
//! Primitive integers `i16`, `i32`, `i64` and `i128` are used to represent
//! values, corresponding to `FixDec16<P>`, `FixDec32<P>`, `FixDec64<P>`,
//! and `FixDec128<P>` types, respectively, which can represent about 4,
//! 9, 18 and 38 decimal significant digits.
//!
//! In addition, these scenarios generally require *fraction precision*,
//! rather than the *significant digits* like in scientific calculations,
//! so fixed-point is more suitable than floating-point.
//!
//! We use Rust's *const generics* to specify the precision. For example,
//! `FixDec16<2>` represents `2` decimal precision and its range represented
//! is `-327.68` ~ `327.67`.
//!
//! # Characteristics
//!
//! It is a common idea to use integers and const generics to represent
//! decimals. We have some specialties.
//!
//! The `+`, `-` and comparison operations only perform between same types in
//! same precision. There is no implicitly type or precision conversion.
//! This makes sence. For example, if you use `FixDec64<2>` to represent
//! balance and `FixDec64<6>` to represent exchange rates, there should be
//! no above operations between balance `FixDec64<2>` and exchange rates
//! `FixDec64<6>`.
//!
//! However, the `*` and `/` operations accept operand with different
//! precisions. Certainly we need to multiply between balance `FixDec64<2>`
//! and exchange rates `FixDec64<6>` to get another balance.
//!
//! Besides, the `*` and `/` operations can specify the precision of the
//! results. For example, the product of balance and exchange rate is still
//! balance, which of another asset, so the result should be `FixDec64<2>`
//! too, but not `FixDec64<2+6>`. Another example, you want to get the
//! exchange rate `FixDec64<6>` by dividing two balance `FixDec64<2>`.
//!
//! # Conversion
//!
//! Meanwhile the conversion can be made explicitly.
//!
//! Different types are converted into each other by `Into` and `TryInto`
//! trait. Use `Into` to convert from less-bit-type to more-bit-type, and
//! use `TryInto` for the opposite direction because it may overflow.
//! The conversion keeps the precision.
//!
//! Different precisions of same type are converted into each other by
//! `rescale()` function.
//!
//! # Features
//!
//! - `serde` enables serde traits integration (`Serialize`/`Deserialize`)
//!
//! # Example
//!
//! Let's see an example of foreign exchange trading.
//!
//! ```
//! use std::str::FromStr;
//! use primitive_fixed_point_decimal::{FixDec64, FixDec16};
//!
//! type Balance = FixDec64<2>;
//! type Price = FixDec64<6>;
//! type FeeRate = FixDec16<4>;
//!
//! // I have 30000 USD and none CNY in my account at first.
//! let mut account_usd = Balance::from_str("30000").unwrap();
//! let mut account_cny = Balance::ZERO;
//!
//! // I want to exchange 10000 USD to CNY at price 7.17, with 0.0015 fee-rate.
//! let pay_usd = Balance::from_str("10000").unwrap();
//! let price = Price::from_str("7.17").unwrap();
//! let fee_rate = FeeRate::from_str("0.0015").unwrap();
//!
//! // Calculate the get_cny = pay_usd * price.
//! // Because `checked_mul()` accepts operand with different precision,
//! // it's not need to convert the `Price` from `FixDec64<8>` to `FixDec64<2>`.
//! // Besides we want get `Balance` as result, so it's need to declare the
//! // `get_cny` as `Balance` explicitly.
//! let get_cny: Balance = pay_usd.checked_mul(price).unwrap();
//!
//! // Calculate the fee_cny = get_cny * fee_rate.
//! // Because `checked_mul()` accepts same type operand only, so the
//! // `FeeRate` is converted from `FixDec16<4>` into `FixDec64<4>`.
//! let fee_cny: Balance = get_cny.checked_mul(fee_rate.into()).unwrap();
//!
//! // Update the trading result.
//! account_usd -= pay_usd;
//! account_cny += get_cny - fee_cny;
//!
//! // Check the result:
//! //   USD = 20000 = 30000 - 10000
//! //   CNY = 71592.45 = 10000 * 7.17 - 10000 * 7.17 * 0.0015
//! assert_eq!(account_usd, Balance::from_str("20000").unwrap());
//! assert_eq!(account_cny, Balance::from_str("71592.45").unwrap());
//! ```
//!
//! # Status
//!
//! More tests are need before ready for production.

mod fixdec16;
mod fixdec32;
mod fixdec64;
mod fixdec128;

#[macro_use]
mod define_macro;
#[macro_use]
mod utils;

pub use crate::fixdec16::DIGITS as FIXDEC16_DIGITS;
pub use crate::fixdec32::DIGITS as FIXDEC32_DIGITS;
pub use crate::fixdec64::DIGITS as FIXDEC64_DIGITS;
pub use crate::fixdec128::DIGITS as FIXDEC128_DIGITS;

pub use crate::fixdec16::FixDec16;
pub use crate::fixdec32::FixDec32;
pub use crate::fixdec64::FixDec64;
pub use crate::fixdec128::FixDec128;

/// Error in converting from string.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseError {
    /// Empty string.
    Empty,
    /// Invalid digit in the string.
    Invalid,
    /// Overflow.
    Overflow,
    /// Too many precisions with Rounding::Error specified.
    Precision,
}

/// Rounding kind.
///
/// This works right for non-negative numbers only by now, for
/// perfomance considerations.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Rounding {
    Round,
    Floor,
    Ceil,
    /// Return Option::None or Result::Err if need rounding.
    Unexpected,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn mul_amount_types() {
        use std::str::FromStr;

        let amount = FixDec64::<8>::from_str("80000").unwrap();
        let rate = FixDec16::<4>::from_str("0.015").unwrap();

        let fee = FixDec64::<8>::from_str("1200").unwrap();
        assert_eq!(amount.checked_mul(rate.into()).unwrap(), fee);
    }
}