7 releases
0.1.6 | Dec 3, 2023 |
---|---|
0.1.5 | Oct 21, 2023 |
0.1.4 | Sep 9, 2023 |
0.1.3 | Jul 29, 2023 |
0.1.2 | May 28, 2023 |
#335 in Finance
38 downloads per month
59KB
739 lines
primitive_fixed_point_decimal
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.
License: MIT
Dependencies
~160KB