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金融

MIT 许可证

59KB
739

primitive_fixed_point_decimal

原始定点小数类型。

在某些场景下,例如在金融领域,需要准确表示小数。原始浮点类型(如 f32f64)无法精确表示小数分数,因为它们使用二进制表示值。在这里,我们使用整数类型来表示值,并以十进制处理分数。

原始整数 i16i32i64i128 用于表示值,分别对应 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>

转换

同时,转换可以显式进行。

不同类型通过 IntoTryInto 特征相互转换。使用 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