3 个版本
0.1.2 | 2021年11月26日 |
---|---|
0.1.1 | 2021年11月26日 |
0.1.0 | 2021年10月29日 |
#985 in 数学
200KB
5K SLoC
注意
此包的开发已停止,转而使用 fpdec.rs。
基于 const generics 的此定点 Decimal 类型实现提供了一些优势
- 紧凑的内存表示(16 字节),
- 非常良好的性能。
将分数位数作为常量类型参数提供给编译器一些额外的优化机会。例如,在 Add 特质的实现中
impl<const P: u8, const Q: u8> Add<Decimal<Q>> for Decimal<P>
where
PrecLimitCheck<{ P <= MAX_PREC }>: True,
PrecLimitCheck<{ Q <= MAX_PREC }>: True,
PrecLimitCheck<{ const_max_u8(P, Q) <= MAX_PREC }>: True,
{
type Output = Decimal<{ const_max_u8(P, Q) }>;
fn add(self, other: Decimal<Q>) -> Self::Output {
match P.cmp(&Q) {
Ordering::Equal => Self::Output {
coeff: Add::add(self.coeff, other.coeff),
},
Ordering::Greater => Self::Output {
coeff: Add::add(
self.coeff,
mul_pow_ten(other.coeff, P - Q),
),
},
Ordering::Less => Self::Output {
coeff: Add::add(
mul_pow_ten(self.coeff, Q - P),
other.coeff,
),
},
}
}
}
对于 P 和 Q 的每一种组合,编译器都可以将 match 语句的代码减少到只有一个情况。
两个 Decimal 的乘法被简化为两个整数(i128)的乘法,因为结果的分数位数在编译时已经确定
impl<const P: u8, const Q: u8> Mul<Decimal<Q>> for Decimal<P>
where
PrecLimitCheck<{ P <= MAX_PREC }>: True,
PrecLimitCheck<{ Q <= MAX_PREC }>: True,
PrecLimitCheck<{ (const_sum_u8(P, Q)) <= MAX_PREC }>: True,
{
type Output = Decimal<{ const_sum_u8(P, Q) }>;
#[inline(always)]
fn mul(self, other: Decimal<Q>) -> Self::Output {
Self::Output {
coeff: self.coeff * other.coeff,
}
}
}
但也有一些严重的缺点
- 泛型函数的大量变体导致二进制代码文件很大。
- 因为每个 Decimal&P; 都是不同的类型,所以存在一些不寻常的不对称性。例如,两个 Decimal&P; 的乘法不会得到 Decimal&P;。即 Decimal&P; 不像其他数值类型那样满足 Mul
。 - 依赖于 nightly 功能。
总的来说,使用 const generics 带来的性能提升并不超过缺点。
遵循与该包相同目标的包 fpdec.rs。它不提供相同的性能,但避免了上述缺点。
----------
此 crate 努力提供一个快速的 Decimal
定点运算实现。
它针对典型商业应用,处理表示数量、金钱等数字,而不是针对科学计算,对于科学计算,浮点数学的精度在大多数情况下是足够的。
目标
- 十进制数字的“精确”表示(与二进制浮点数相比没有偏差)
- 没有隐藏的舍入错误(如浮点数学固有的错误)
- 非常快速的运算(通过将其映射到整数运算)
- 表示的十进制数字范围足以满足典型商业应用
在二进制级别上,十进制数表示为一个系数(存储为 i128
值)与一个类型参数组合,该参数指定小数位的数量。也就是说,整个实现基于“const generics”,需要支持此功能的 Rust 版本。
状态
实验性(开发中)
入门
将 rust-fixed-point-decimal
添加到您的 Cargo.toml
[dependencies]
rust-fixed-point-decimal = "0.1"
注意
由于“const generics”的实现尚不完整,您必须在 main.rs 或 lib.rs 文件的开始处添加以下内容
#![allow(incomplete_features)]
#![feature(generic_const_exprs)]
用法
可以以不同的方式创建 Decimal
数。
最简单的方法是使用过程宏 Dec
# use rust_fixed_point_decimal::Dec;
let d = Dec!(-17.5);
assert_eq!(d.to_string(), "-17.5");
或者,您可以将整数、浮点数或字符串转换为 Decimal
# use rust_fixed_point_decimal::Decimal;
let d = Decimal::<2>::from(297_i32);
assert_eq!(d.to_string(), "297.00");
# #![allow(incomplete_features)]
# #![feature(generic_const_exprs)]
# use rust_fixed_point_decimal::{Decimal, DecimalError};
# use std::convert::TryFrom;
let d = Decimal::<5>::try_from(83.0025)?;
assert_eq!(d.to_string(), "83.00250");
# Ok::<(), DecimalError>(())
# #![allow(incomplete_features)]
# #![feature(generic_const_exprs)]
# use rust_fixed_point_decimal::{Decimal, ParseDecimalError};
# use std::str::FromStr;
let d = Decimal::<4>::from_str("38.207")?;
assert_eq!(d.to_string(), "38.2070");
# Ok::<(), ParseDecimalError>(())
可以使用一元减号运算符和 Decimal
实例反转 Decimal
的符号,并且可以将 Decimal
实例与类型 Decimal
的其他实例或所有整数基本类型(除了 u128 和 atm 之外的 u8,这会导致编译器错误)进行比较
# #![allow(incomplete_features)]
# #![feature(generic_const_exprs)]
# use rust_fixed_point_decimal::{Dec, Decimal};
let x = Dec!(129.24);
let y = -x;
assert_eq!(y.to_string(), "-129.24");
assert!(-129_i64 > y);
let z = -y;
assert_eq!(x, z);
let z = Dec!(0.00097);
assert!(x > z);
assert!(y <= z);
assert!(z != 7_u32);
assert!(7_u32 == Dec!(7.00));
Decimal
支持所有五个二进制数值运算符 +, -, *, /, 和 %,可以用于两个 Decimal
或一个 Decimal
和一个基本整数(除了 u128)
# #![allow(incomplete_features)]
# #![feature(generic_const_exprs)]
# use rust_fixed_point_decimal::{Dec, Decimal};
let x = Dec!(17.5);
let y = Dec!(6.40);
let z = x + y;
assert_eq!(z.to_string(), "23.90");
let z = x - y;
assert_eq!(z.to_string(), "11.10");
let z = x * y;
assert_eq!(z.to_string(), "112.000");
let z = x / y;
assert_eq!(z.to_string(), "2.734375000");
let z = x % y;
assert_eq!(z.to_string(), "4.70");
# #![allow(incomplete_features)]
# #![feature(generic_const_exprs)]
# use rust_fixed_point_decimal::{Dec, Decimal};
let x = Dec!(17.5);
let y = -5_i64;
let z = x + y;
assert_eq!(z.to_string(), "12.5");
let z = x - y;
assert_eq!(z.to_string(), "22.5");
let z = y * x;
assert_eq!(z.to_string(), "-87.5");
let z = x / y;
assert_eq!(z.to_string(), "-3.500000000");
let z = x % y;
assert_eq!(z.to_string(), "2.5");
所有这些二进制数值运算符在结果无法根据上述约束表示为 Decimal
时会引发 panic。此外,还有实现运算符“checked”变体的函数,它们在 panic 而不是返回 Option::None
对于乘法和除法,也有返回结果四舍五入到目标类型指定的分数位的函数
# #![allow(incomplete_features)]
# #![feature(generic_const_exprs)]
# use rust_fixed_point_decimal::{Dec, Decimal, DivRounded, MulRounded};
let x = Dec!(17.5);
let y = Dec!(6.47);
let z: Decimal<1> = x.mul_rounded(y);
assert_eq!(z.to_string(), "113.2");
let z: Decimal<3> = x.div_rounded(y);
assert_eq!(z.to_string(), "2.705");
依赖项
~405–550KB
~11K SLoC