#decimal-number #decimal #numbers #fixed-point #arithmetic #decimal-digits

rust-fixed-point-decimal-macros

用于简化十进制数字实例化的宏

3 个版本

0.1.2 2021年11月26日
0.1.1 2021年11月26日
0.1.0 2021年10月29日

#9 in #decimal-digits


用于 rust-fixed-point-decimal

自定义许可

18KB
316

注意

本软件包的开发已停止,转而使用 fpdec.rs

由于基于 const generics,此固定点十进制类型的实现提供了一些优势

  • 紧凑的内存表示(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 的每个组合,编译器可以将匹配语句的代码减少到只有一个情况。

并且两个十进制数的乘法简化为两个整数的乘法(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<Self, Output = Self>。
  • 依赖于 nightly 功能。

总的来说,使用 const generics 带来的性能提升并不足以抵消缺点。

软件包 fpdec.rs 与本软件包具有相同的目标。它不提供相同的性能,但避免了上述缺点。

----------

此软件包努力提供快速实现 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 的实例或所有基本整数类型(除了 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");

依赖

~87KB