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