1 个不稳定版本
0.0.1 | 2020年4月6日 |
---|
#42 在 #financial
51KB
1K SLoC
Merx
mèr | ce s. f. [lat. merx mercis]: 可分割的、可分配的部分。
预测试版本 如果您认为 merx 有用或喜欢它,请在这里 [link TODO] 提出一个关于这个库应走哪个方向的讨论线程。
rustc 版本 >= (1edd389cc 2020-03-23)
Merx 是一个有用的库,可以安全地讨论数量并关注性能。它受到了这篇文章的启发 Safe Decimal Right on the Money from fpcomplete.
工作原理
Merx 允许您定义资产。资产是一切具有数量并且可以分割的东西,例如资产可以是货币、商品、(一个物理数量?)等等
资产由最小数量(单位)定义,这是您拥有的或可以讨论的最小资产部分。有时(很多)可以为资产想出一个上限,因此我们可以定义它,并使创建过大的值变得不可能。
支持同一类型的资产之间的加法和减法,使用操作符重载。乘法和除法通过操作符重载在资产和数字类型之间实现。
资产不能欠负数,但可以是贷方或借方,因此必须明确声明负数是可选的。(如果一个函数接受资产,则表示它可以在正数或负数上工作,但当一个函数接受贷方或借方时,您可以确信它只在工作正数或负数上。
每次资产的数量超过上限时 [返回错误][TODO]。如果一个资产没有指定上限,那么 [代码] i128
被用作资产的内部值,并将上限设置为 i128::max_value()
[TODO]。
当我们添加/减去资产或将资产乘以一个数字时,会检查溢出,并在溢出的情况下返回错误。
待办:可以设置两种不同资产之间的汇率,然后可以(明确地)将一种资产转换为另一种资产,反之亦然。
Merx公开了Asset
,它是一个包装在Debt
或Credit
上的包装器,这些包装器又包装在一个数值上,目前它们只与一个虚拟的内部固定值一起工作,但我想让它通用,以便在定义资产时可以选择。
pub struct <T: NUMERIC>Debt(T);
pub struct <T: NUMERIC>Credit(T);
pub enum Asset<T: NUMERIC> {
Debt(Debt(T)),NUMERIC
Credit(Credit(T)),
}
允许的操作有:Credit - Debt
Credit + Credit
Debt + Credit
Debt + Debt
Asset + Asset
。因为Credit
不能有负值,所以Debt
不能有正值,而Asset
要么是Credit
要么是Debt
,在我看来,资产之间的加法和减法并不明确,在我看来,更有意义的是
- 只存在add(A, B):
Asset(x) + Asset(-y) = Asset(x + (-y))
- add(A, B) == sub(A, B)
Asset(x) + Asset(-y) = Asset(x + (-y)) && Asset(x) - Asset(-y) = Asset(x + (-y))
- add(A, B) == sub(A, -B)
Asset(x) + Asset(-y) = Asset(x + (-y)) && Asset(x) - Asset(-y) = Asset(x - (-y))
目前add和sub的行为类似于(1),因为我认为这是最不容易出错的,而merx的主要目标是安全性;顺便说一句,可用性也很重要,我认为(1)不太可用。
示例
#[macro_use]
extern crate merx;
use merx::{Asset, Debt, Credit, asset::CheckedOps};
get_traits!();
// Create a new asset called bitcoin with 8 decimal digits and a max value of 21 million of units
new_asset!(bitcoin, 8, 21_000_000);
// Create a new asset called usd with 2 decimal digits and a max value of 14_000_000_000_000 units
new_asset!(usd, 2, 14_000_000_000_000);
type Bitcoin = Asset<bitcoin::Value>;
type Usd = Asset<usd::Value>;
fn main() {
// A tuple that define a decimal value as (mantissa, decimal part)
let tot_amount = (679, 1); // -> 67.9
let tot_amount = Bitcoin::try_from(tot_amount).unwrap();
let to_pay = Bitcoin::try_from(-29).unwrap();
let remain = (tot_amount + to_pay).unwrap();
println!("{:#?}", remain);
// TODO smouthly conversion
//let x: USD = match remain {
// Credit(x) => interests(USD::from(x), 12, 3);
// Debt(x) => interests(USD::from(x), 12, 3);
//};
}
// You can define function over generic assets:
// Adding assets of type T return an asset of type T
fn add_assets<T: CheckedOps>(x: Asset<T>, y: Asset<T>) -> Option<Asset<T>> {
x + y
}
// Adding credits can only result in a Credit
fn add_credits<T: CheckedOps>(x: Credit<T>, y: Credit<T>) -> Option<Credit<T>> {
x + y
}
// Adding debts can only result in a Debt
fn add_debts<T: CheckedOps>(x: Debt<T>, y: Debt<T>) -> Option<Debt<T>> {
x + y
}
// Adding debts can only result in a Debt
fn add_debts2<T: CheckedOps>(x: Debt<T>, y: Debt<T>) -> Option<Debt<T>> {
x + y
}
安全性
- 不可能将不同类型的资产或带数值的资产相加。
- 所有涉及资产的操作(加法、乘法、除法)都会进行检查,并在不正确的值上失败。
- 从原始类型构建资产是安全的 [待办]。
- 当操作的结果为正数时,我们得到一个
Credit
,否则我们得到一个Debt
,不可能用负值构建一个Credit
或用正值构建一个Debt
。 - 该库没有依赖项。
性能
在内部添加资产意味着执行一个checked_add
并检查值是否小于或等于最大值。看起来库在执行这一操作时比下面这样的普通函数要快一些。
fn checked_add_and_compare_64(a: i64, b: i64, max: i64) -> Option<i64> {
let sum = a.checked_add(b)?;
if sum.checked_abs()? <= max {
return Some(sum);
}
return None
}
Benchmarking add 64 bit int
Benchmarking add 64 bit int: Warming up for 3.0000 s
Benchmarking add 64 bit int: Collecting 100 samples in estimated 5.0000 s (2.5B iterations)
Benchmarking add 64 bit int: Analyzing
add 64 bit int time: [2.0150 ns 2.0205 ns 2.0281 ns]
change: [-1.3576% -0.5189% +0.0981%] (p = 0.19 > 0.05)
No change in performance detected.
Found 8 outliers among 100 measurements (8.00%)
3 (3.00%) high mild
5 (5.00%) high severe
Benchmarking add 64 bit assets
Benchmarking add 64 bit assets: Warming up for 3.0000 s
Benchmarking add 64 bit assets: Collecting 100 samples in estimated 5.0000 s (2.5B iterations)
Benchmarking add 64 bit assets: Analyzing
add 64 bit assets time: [2.0138 ns 2.0168 ns 2.0210 ns]
change: [-1.0088% -0.3617% +0.1265%] (p = 0.25 > 0.05)
No change in performance detected.
Found 9 outliers among 100 measurements (9.00%)
3 (3.00%) high mild
6 (6.00%) high severe
当资产被乘以或除以时也会发生这种情况。我认为库并不慢,但我不能为上面的数字做出解释。我对这些基准测试以及我的基准测试能力不太自信,这些基准测试必须进行审查。
精度
[待办事项]
替代方案
我能找到的最相似的可 crate 是 commodity,merx 与 commodity 在 ... TODO 有所不同
以下是一些解决与 merx 解决的问题相似或相关的问题的 crates 列表。
定点运算
- fixed.
- ... 待办事项
十进制
货币 crates [待办事项]
单位 crates
- yaiouom
- ... 待办事项
待办事项
- 使用 crate fixed 作为内部类型(当它支持泛型常量时)
- 为 Asset 和所有原始数值类型实现 PartialEq
- 使用 thiserror 添加错误
- Serde 序列化和反序列化
- 资产、浮点数之间以及资产和 fixed 之间的除法和乘法
- 添加所有标准的有理数操作,如截断、floor 等
- 添加资产之间的转换
- 许多公共事物应该是私有的
- 基准测试
- 文档
- 使用 bigint 而不是定点数进行精确模式
- 添加在定义资产时定义舍入策略的可能性
许可证
MIT 或 UNLICENSE