#fixed #wrapper #currency #money #finance

nightly merx

Merx 是一个有用的库,可以安全地讨论数量并关注性能

1 个不稳定版本

0.0.1 2020年4月6日

#42#financial

MIT OR Unlicense

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,它是一个包装在DebtCredit上的包装器,这些包装器又包装在一个数值上,目前它们只与一个虚拟的内部固定值一起工作,但我想让它通用,以便在定义资产时可以选择。

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,在我看来,资产之间的加法和减法并不明确,在我看来,更有意义的是

  1. 只存在add(A, B):Asset(x) + Asset(-y) = Asset(x + (-y))
  2. add(A, B) == sub(A, B) Asset(x) + Asset(-y) = Asset(x + (-y)) && Asset(x) - Asset(-y) = Asset(x + (-y))
  3. 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
}

安全性

  1. 不可能将不同类型的资产或带数值的资产相加。
  2. 所有涉及资产的操作(加法、乘法、除法)都会进行检查,并在不正确的值上失败。
  3. 从原始类型构建资产是安全的 [待办]。
  4. 当操作的结果为正数时,我们得到一个Credit,否则我们得到一个Debt,不可能用负值构建一个Credit或用正值构建一个Debt
  5. 该库没有依赖项。

性能

在内部添加资产意味着执行一个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

待办事项

  • 使用 crate fixed 作为内部类型(当它支持泛型常量时)
  • 为 Asset 和所有原始数值类型实现 PartialEq
  • 使用 thiserror 添加错误
  • Serde 序列化和反序列化
  • 资产、浮点数之间以及资产和 fixed 之间的除法和乘法
  • 添加所有标准的有理数操作,如截断、floor 等
  • 添加资产之间的转换
  • 许多公共事物应该是私有的
  • 基准测试
  • 文档
  • 使用 bigint 而不是定点数进行精确模式
  • 添加在定义资产时定义舍入策略的可能性

许可证

MIT 或 UNLICENSE

无运行时依赖项