3 个版本 (破坏性更新)

0.10.3 2024年2月11日
0.10.1 2024年1月21日
0.10.0 2023年12月14日
0.8.1 2023年6月28日
0.2.0 2021年11月29日

#89 in 数据结构

Download history 318/week @ 2024-04-09 266/week @ 2024-04-16 178/week @ 2024-04-23 145/week @ 2024-04-30 211/week @ 2024-05-07 229/week @ 2024-05-14 282/week @ 2024-05-21 143/week @ 2024-05-28 152/week @ 2024-06-04 180/week @ 2024-06-11 455/week @ 2024-06-18 348/week @ 2024-06-25 486/week @ 2024-07-02 384/week @ 2024-07-09 357/week @ 2024-07-16 286/week @ 2024-07-23

每月 1,586 次下载
4 crate 中使用

自定义许可

285KB
7K SLoC

此 crate 提供了 Decimal 定点算术的快速实现。它针对典型的商业应用,处理表示数量、货币等数值,而不是针对科学计算,对于后者,浮点数学的精度在大多数情况下是足够的。

目标

  • 十进制数的“精确”表示(没有像二进制浮点数那样的偏差)
  • 没有隐藏的舍入误差(如浮点数学固有的)
  • 非常快速的操作(通过将它们映射到整数操作)
  • 可表示的十进制数的范围足以满足典型的商业应用

在二进制级别,Decimal 数表示为一个系数(存储为 i128 值)和一个指定小数位数(存储为 u8)。后者限制为常量 MAX_N_FRAC_DIGITS = 18 的值。

状态

正在进行中,但大多数 API 都是稳定的。

入门

fpdec 添加到您的 Cargo.toml

[dependencies]
fpdec = "0.10"

用法

Decimal 数可以以不同的方式创建。

最简单的方法是使用过程宏 Dec

# use fpdec::{Dec, Decimal};
let d = Dec!(-17.5);
assert_eq!(d.to_string(), "-17.5");

或者,您可以转换整数、浮点数或字符串为 Decimal

# use fpdec::Decimal;
let d = Decimal::from(297_i32);
assert_eq!(d.to_string(), "297");
# use fpdec::{Decimal, DecimalError};
# use core::convert::TryFrom;
let d = Decimal::try_from(83.25_f64)?;
assert_eq!(d.to_string(), "83.25");
# Ok::<(), DecimalError>(())
# use fpdec::{Decimal, ParseDecimalError};
# use core::str::FromStr;
let d = Decimal::from_str("38.2070")?;
assert_eq!(d.to_string(), "38.2070");
# Ok::<(), ParseDecimalError>(())

可以使用一元减号运算符反转 Decimal 的符号,并且可以比较 Decimal 实例与其他类型的 Decimal 实例或所有基本整数类型(除了 u128)

# use fpdec::{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)之间。

# use fpdec::{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.734375");
let z = x % y;
assert_eq!(z.to_string(), "4.70");
# use fpdec::{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.5");
let z = x % y;
assert_eq!(z.to_string(), "2.5");

乘法或除法的结果在任何情况下都不是精确的。如果精确结果的分数小数位数超过 MAX_N_FRAC_DIGITS 分数小数位数,则结果会四舍五入以适应此限制。

# use fpdec::{Dec, Decimal};
let x = Dec!(1e-10);
let y = Dec!(75e-9);
let z = x * y;
assert_eq!(z.to_string(), "0.000000000000000008");
let x = Dec!(1.);
let y = Dec!(3.);
let z = x / y;
assert_eq!(z.to_string(), "0.333333333333333333");

所有这些二进制数值运算符如果结果不能按照上述约束表示为 Decimal,则会引发 panic。此外,还有实现“已检查”运算符变体的函数,这些函数在失败时返回 Option::None 而不是 panic。

对于乘法和除法,还有返回四舍五入到指定小数位数的结果的函数。

# use fpdec::{Dec, Decimal, DivRounded, MulRounded};
let x = Dec!(17.5);
let y = Dec!(6.47);
let z: Decimal = x.mul_rounded(y, 1);
assert_eq!(z.to_string(), "113.2");
let z: Decimal = x.div_rounded(y, 3);
assert_eq!(z.to_string(), "2.705");

Decimal 值可以被转换成浮点数,可能四舍五入到目标类型可以表示的最接近的值。

# use fpdec::{Dec, Decimal};
let d = Dec!(-33820900478.195);
let f = f64::from(d);
assert_eq!(f, -33820900478.19499969482421875_f64);
let f = f32::from(Dec!(0.6));
assert_eq!(f, 0.60000002384185791015625_f32);

Decimal 值转换为原始 int 类型更为复杂。它只通过 try_from / try_into 支持,并且只有当给定的值代表适合目标类型值范围的整数时,才提供目标类型的值。

# use fpdec::{Dec, Decimal, TryFromDecimalError};
let d = Dec!(3.7);
let res = i32::try_from(d);
assert!(res.is_err());
assert_eq!(res.unwrap_err(), TryFromDecimalError::NotAnIntValue);
let d = Decimal::MAX;
let res = i128::try_from(d);
assert_eq!(res.unwrap(), i128::MAX);
let res = i64::try_from(d);
assert!(res.is_err());
assert_eq!(res.unwrap_err(), TryFromDecimalError::ValueOutOfRange);

仓库特性

默认情况下,仅启用 std 特性。

生态系统

  • std - 当启用时,这将导致 fpdec 使用标准库,因此可以提供字符串转换、格式化和打印功能。当禁用时,需要使用 crate alloc 和特定于系统的分配器来使用这些功能。

  • packed - 当启用时,结构体 Decimal 被标记为 #[repr(packed)]

可选依赖

  • num-traits - 当启用时,为 Decimal 实现 num-traits::Num 特性。

  • serde-as-str - 当启用时,启用对 serde 的支持。这允许 Decimal 实例作为字符串序列化,并可以通过 serde 从字符串反序列化。

  • rkyv - 当启用时,启用对 rkyv 的支持。这允许 Decimal 实例通过 rkyv 归档进行零拷贝序列化和反序列化。

依赖项

~18–460KB
~10K SLoC