7 个版本
0.1.6 | 2023 年 12 月 3 日 |
---|---|
0.1.5 | 2023 年 10 月 21 日 |
0.1.4 | 2023 年 9 月 9 日 |
0.1.3 | 2023 年 7 月 29 日 |
0.1.2 | 2023 年 5 月 28 日 |
#289 在 金融
59KB
739 行
primitive_fixed_point_decimal
原始定点小数类型。
在某些场景下,例如在金融领域,需要准确表示小数。原始浮点类型(如 f32 和
f64
)无法精确表示小数分数,因为它们使用二进制表示值。在这里,我们使用整数类型来表示值,并以十进制处理分数。
原始整数 i16
、i32
、i64
和 i128
用于表示值,分别对应 FixDec16<P>
、FixDec32<P>
、FixDec64<P>
和 FixDec128<P>
类型,分别可以表示约 4、9、18 和 38 位十进制有效数字。
此外,这些场景通常需要 分数精度,而不是像科学计算中那样的 有效数字,因此定点数比浮点数更适合。
我们使用 Rust 的 const generics 来指定精度。例如,FixDec16<2>
表示 2
位小数精度,其表示的范围是 -327.68
~ 327.67
。
特点
使用整数和 const generics 来表示小数是一个常见的想法。我们有一些特殊之处。
加法(+)、减法(-)和比较运算只对相同类型和精度的数据进行操作。没有隐式类型或精度转换。这是有意义的。例如,如果您使用 FixDec64<2>
来表示余额,使用 FixDec64<6>
来表示汇率,则不应在余额 FixDec64<2>
和汇率 FixDec64<6>
之间进行上述操作。
然而,乘法(*)和除法(/)操作接受不同精度的操作数。当然,我们需要将余额 FixDec64<2>
和汇率 FixDec64<6>
相乘以获得另一个余额。
此外,乘法(*)和除法(/)操作可以指定结果的精度。例如,余额和汇率的乘积仍然是另一种资产的余额,因此结果也应为 FixDec64<2>
,而不是 FixDec64<2+6>
。另一个例子,您想通过除以两个余额 FixDec64<2>
来获得汇率 FixDec64<6>
。
转换
同时,转换可以显式进行。
不同类型通过 Into
和 TryInto
特征相互转换。使用 Into
将从少位类型转换为多位类型,使用 TryInto
进行相反方向的转换,因为它可能会导致溢出。转换保持精度。
相同类型的不同精度通过 rescale
函数相互转换。
特性
serde
允许与 serde 特征集成(Serialize
/Deserialize
)
示例
让我们看看外汇交易的一个例子。
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());
状态
在准备生产之前需要更多测试。
许可证:MIT
依赖关系
~170KB