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 日

#150Rust 模式

Download history 490198/week @ 2024-03-14 503127/week @ 2024-03-21 516418/week @ 2024-03-28 512156/week @ 2024-04-04 518559/week @ 2024-04-11 545458/week @ 2024-04-18 539197/week @ 2024-04-25 527058/week @ 2024-05-02 526798/week @ 2024-05-09 557024/week @ 2024-05-16 557611/week @ 2024-05-23 610174/week @ 2024-05-30 580298/week @ 2024-06-06 569471/week @ 2024-06-13 554961/week @ 2024-06-20 473582/week @ 2024-06-27

2,295,838 每月下载量
2,349 个crate中使用 (215 直接)

MIT 许可证

46KB
768

float-cmp

Build Status MIT licensed

文档可在 https://docs.rs/float-cmp 找到

float-cmp 定义并实现了用于近似比较浮点类型的特质,这些类型由于浮点表示中可用的精度有限,已经偏离了精确相等。为 f32f64 类型提供了这些特质的实现。

在我 80 年代还是个孩子的时候,编程规则是“永远不要比较浮点数”。如果你能遵循这条规则并获得你想要的结果,那么就太好了。然而,如果你真的需要比较它们,这个 crate 提供了一种合理的方式来这样做。

另一个 crate efloat 通过提供一个在执行操作时跟踪其误差界限的浮点数类型,提供了另一种解决方案,并可以在不指定 Margin 的情况下更准确地实现此 crate 中的 ApproxEq 特质。

推荐的解决方案(尽管它可能并不适用于所有情况)是 approx_eq() 函数在 ApproxEq 特质中(或者更好的是,宏)。对于 f32f64,提供了 F32MarginF64Margin 类型来指定边缘,作为 epsilon 值和 ULPs 值,并通过 Default 提供默认值(尽管没有绝对合适的默认值,所以要注意)。

还提供了其他几个特质,包括 UlpsApproxEqUlpsApproxOrdUlpsApproxEqRatio

问题

浮点数操作必须将结果四舍五入到最接近的可表示数。多次操作可能会导致一个结果与您预期的不同。在下面的示例中,断言将失败,尽管打印的输出说 "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