22 个版本
| 0.9.0 | 2021 年 7 月 21 日 |
|---|---|
| 0.8.0 | 2020 年 5 月 4 日 |
| 0.7.0 | 2020 年 4 月 28 日 |
| 0.6.0 | 2019 年 11 月 18 日 |
| 0.0.3 | 2015 年 3 月 10 日 |
#150 在 Rust 模式 中
2,295,838 每月下载量
在 2,349 个crate中使用 (215 直接)
46KB
768 行
float-cmp
文档可在 https://docs.rs/float-cmp 找到
float-cmp 定义并实现了用于近似比较浮点类型的特质,这些类型由于浮点表示中可用的精度有限,已经偏离了精确相等。为 f32 和 f64 类型提供了这些特质的实现。
在我 80 年代还是个孩子的时候,编程规则是“永远不要比较浮点数”。如果你能遵循这条规则并获得你想要的结果,那么就太好了。然而,如果你真的需要比较它们,这个 crate 提供了一种合理的方式来这样做。
另一个 crate efloat 通过提供一个在执行操作时跟踪其误差界限的浮点数类型,提供了另一种解决方案,并可以在不指定 Margin 的情况下更准确地实现此 crate 中的 ApproxEq 特质。
推荐的解决方案(尽管它可能并不适用于所有情况)是 approx_eq() 函数在 ApproxEq 特质中(或者更好的是,宏)。对于 f32 和 f64,提供了 F32Margin 和 F64Margin 类型来指定边缘,作为 epsilon 值和 ULPs 值,并通过 Default 提供默认值(尽管没有绝对合适的默认值,所以要注意)。
还提供了其他几个特质,包括 Ulps、ApproxEqUlps、ApproxOrdUlps 和 ApproxEqRatio。
问题
浮点数操作必须将结果四舍五入到最接近的可表示数。多次操作可能会导致一个结果与您预期的不同。在下面的示例中,断言将失败,尽管打印的输出说 "0.45 == 0.45"。
let a: f32 = 0.15 + 0.15 + 0.15;
let b: f32 = 0.1 + 0.1 + 0.25;
println!("{} == {}", a, b);
assert!(a==b) // Fails, because they are not exactly equal
这失败了,因为大多数操作的正确答案无法精确表示,所以您的计算机处理器选择用它可用的最接近的值来表示答案。这引入了误差,而这种误差在执行多个操作时可能会累积。
解决方案
使用 ApproxEq,我们可以得到我们想要的答案
let a: f32 = 0.15 + 0.15 + 0.15;
let b: f32 = 0.1 + 0.1 + 0.25;
println!("{} == {}", a, b);
assert!( approx_eq!(f32, a, b, ulps = 2) );
一些解释
我们使用术语ULP(最小精度单位,或最低有效位单位)来表示两个相邻浮点表示之间的差异(相邻意味着它们之间没有浮点数)。这个术语是从先前的工作中借用的(我个人会选择“量子”)。ULP的大小(按浮点数测量)取决于相关浮点数的指数。这是一件好事,因为随着数值由于表示的不精确性而远离相等,它们以ULP为单位而不是以绝对值为单位而远离。纯epsilon比较是绝对的,因此不适合映射到加法误差问题的性质。它们适用于许多数字范围,但不适用于其他范围(例如比较 -0.0000000028 和 +0.00000097)。
使用这个库
默认情况下,此库启用 ratio 模块,提供 ApproxEqRatio 特性。此功能会引入 num-traits。如果您禁用此功能,则需要直接启用 num-traits 或启用 std 特性;否则无法编译。除非启用 std 特性,否则此库是 #![no_std]。
您可以直接像这样使用 ApproxEq 特性
assert!( a.approx_eq(b, F32Margin { ulps: 2, epsilon: 0.0 }) );
我们为 F32Margin(以及类似地为 F64Margin)实现了 From<(f32,i32)>,因此您可以使用此简写
assert!( a.approx_eq(b, (0.0, 2)) );
使用宏,可以更明确地说明您想设置哪种类型的边界,而无需提及另一种类型(另一种类型将为零)。但缺点是您必须指定您正在处理的数据类型
assert!( approx_eq!(f32, a, b, ulps = 2) );
assert!( approx_eq!(f32, a, b, epsilon = 0.00000003) );
assert!( approx_eq!(f32, a, b, epsilon = 0.00000003, ulps = 2) );
assert!( approx_eq!(f32, a, b, (0.0, 2)) );
assert!( approx_eq!(f32, a, b, F32Margin { epsilon: 0.0, ulps: 2 }) );
assert!( approx_eq!(f32, a, b, F32Margin::default()) );
assert!( approx_eq!(f32, a, b) ); // uses the default
对于大多数情况,我建议您为 ulps 参数使用一个小整数值(1到5左右),以及浮点数EPSILON常数的相似小倍数(1.0到5.0左右),但有许多情况下这不足够。
实现这些特性
您可以像下面所示一样为您自己的复杂类型实现 ApproxEq。浮点类型 F 必须是 Copy,但对于大型类型,您可以像下面这样为您的类型的引用实现它。
use float_cmp::ApproxEq;
pub struct Vec2<F> {
pub x: F,
pub y: F,
}
impl<'a, M: Copy, F: Copy + ApproxEq<Margin=M>> ApproxEq for &'a Vec2<F> {
type Margin = M;
fn approx_eq<T: Into<Self::Margin>>(self, other: Self, margin: T) -> bool {
let margin = margin.into();
self.x.approx_eq(other.x, margin)
&& self.y.approx_eq(other.y, margin)
}
}
非浮点类型
ApproxEq 也可以用于实现非浮点类型,因为 Margin 是一个关联类型。
在 efloat 库中,通过检查误差界限是否重叠来实现(或很快将实现)ApproxEq 对于跟踪浮点误差界限的复合类型。在这种情况下,type Margin = ()。
灵感
此库是从这篇随机ASCII博客文章中受到启发的
https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
依赖项
~38KB