没有使用epsilon,直接将浮点数与0.0进行比较是否可行?

21

我知道,要比较两个浮点数值,需要使用一些 epsilon 精度,因为它们并不是精确的。然而,我想知道是否存在某些极端情况,我不需要使用 epsilon。

特别地,我想知道是否总是安全的像这样做:

double foo(double x){
    if (x < 0.0) return 0.0;
    else return somethingelse(x); // somethingelse(x) != 0.0
}

int main(){
   int x = -3.0;
   if (foo(x) == 0.0) { 
     std::cout << "^- is this comparison ok?" << std::endl; 
   }
}

我知道有更好的方法来编写foo(例如,同时返回一个标志),但我想知道通常将0.0赋值给浮点变量并稍后将其与0.0进行比较是否可以。

更一般地说,以下比较是否总是为true?

double x = 3.3;
double y = 3.3;
if (x == y) { std::cout << "is an epsilon required here?" << std::endl; }

我尝试过,似乎可以工作,但不能依赖它。


1
你为什么认为在这方面0.0是特殊的? - Oliver Charlesworth
6
因为我认为如果一个浮点数不能被精确表示,那么0.0也应该可以。 - 463035818_is_not_a_number
10
你的前提是错误的。比较浮点数是完全可能和合理的。不准确的只有“操作”,而不是数据类型本身。 - Kerrek SB
2
为什么 3.3 不等于 3.3 呢?作为基于二进制的数字,其精确值可能不同,但肯定是以相同的方式不同。我认为这些应该是相等的。 - Johannes Schaub - litb
1
@tobi303:你可能也会对这个感兴趣:https://dev59.com/-2Ei5IYBdhLWcg3wRauf - Ben Voigt
显示剩余20条评论
5个回答

11

在这个例子中,检查== 0.0是完全可以的。这不是因为0.0有什么特别之处,而是因为您只是赋值一个值然后进行比较。您也可以将其设置为3.3并比较== 3.3,这也是可以的。您正在存储一个位模式,并对该完全相同的位模式进行比较,只要在进行比较时它们没有被提升为另一种类型。

然而,数学上等于零的计算结果不总是等于0.0


这个问题的答案已经发展到包括不同部分的程序由不同的编译器编译的情况。问题没有提及这一点,我的答案仅适用于所有相关部分使用相同编译器的情况。

C++11标准,
§5.10 相等运算符

 

6 如果两个操作数都是算术或枚举类型,则对两个操作数执行通常算术转换;如果指定的关系为真,则每个运算符应产生true,如果为假,则产生false。

关系没有进一步定义,因此我们必须使用“相等”的常见含义。

§2.13.4 浮点文字

 

1[...]如果缩放值在其类型的可表示值范围内,则如果可表示,则结果为缩放值,否则是最接近缩放值的较大或较小可表示值,以实现定义的方式选择。 [...]

当值不可表示时,编译器必须在两个值之间进行选择。如果同一字面量始终选择相同的值,则可以安全地比较例如3.3之类的值,因为==表示“相等”。


你如何保证只有一种比特模式表示“0.0”?如果有两种同样好的比特模式表示“0.0”,例如从内存加载得到的一种和直接加载零到内部浮点单元得到的另一种,那该怎么办? - David Schwartz
2
你错了,0.0 在非常独特的方式下是特殊的。这就是为什么你可以用它做一些其他浮点数无法实现的操作。 - Eric Leschinski
3
@EricLeschinski,你有这个声明的参考资料吗? - David Schwartz
2
这并不是一个关于0.0是否可以用一个位模式表示的问题,而是与浮点数比较相关的浮点标准所说的。现在很少能找到一台不遵循IEEE-754或其某个近似派生标准的机器,这保证了将+0.0与-0.0进行比较会得到“相等”的结果。当然,如果你做类似于y = foo(x); z = 0.0; if (memcmp(&y, &z, sizeof(z)) == 0) { it_is_zero(); }这样的事情,那么你必须确保位模式匹配。 - Mats Petersson
2
0.0在十进制或二进制中都没有重复的尾数,因此它是特殊的。还有其他特殊的浮点值,它们在十进制和转换后的二进制中都在17个单位精度之前终止尾数。浮点比较的危险在于从十进制到二进制的转换,而不是双等号部分。参考:https://www.youtube.com/watch?v=PZRI1IfStY0 。0的特殊性在于尾数是否在十进制转二进制转换器截断其余部分之前终止。再次阅读您的答案后,您并没有错。 - Eric Leschinski
显示剩余5条评论

8

如果你返回0.0,你可以将其与0.0进行比较; 0可以准确地表示为浮点值。 如果您返回3.3,则必须更加仔细,因为3.3无法准确表示,因此例如从double转换为float将产生不同的值。


2
问题不在于零是否可以准确地表示,而在于它是否可以唯一地表示。 - David Schwartz
6
我认为问题不在于此。问题在于比较两个零是否为真。即使这两个零以不同的方式表示,也可能是这种情况。 - Johannes Schaub - litb
2
如果您正在比较的值是计算结果,那么恰好可表示并不起任何帮助作用。因此,虽然在特定示例中安全,但通常不安全。 - Clifford
1
@DavidSchwartz - 这表明没有浮点行为是有保证的,也不可能有保证。由于你没有绝对的东西,你(吸气!)必须对合理性做出假设。 - Pete Becker
2
@PeteBecker 抱歉,我不同意你的观点。作为C++标准的一部分,你有自己的标准。询问该标准提供了哪些保证和不保证是完全合理的。 - David Schwartz
显示剩余12条评论

2

更正:作为浮点数,0并不是唯一的,但IEEE 754定义了比较0.0==-0.0返回true(实际上任何一个0都是如此)。

所以对于0.0这个值,这种比较是可行的,但对于其他数字则不行。例如,在一个编译单元(例如库)中,字面值3.3可能与另一个编译单元(例如您的应用程序)中的字面值不同。标准只要求编译器使用与运行时相同的舍入方式,但不同的编译器/编译器设置可能使用不同的舍入方式。

这种方法在大多数情况下(对于0)可以工作,但是非常不好的做法。

只要您使用相同的编译器和相同的设置(例如一个编译单元),它就可以工作,因为字面值0.00.0f每次都会转换为相同的位模式。然而,零的表示不是唯一的。因此,如果foo在库中声明,并且您在某个应用程序中调用它,则同一函数可能会失败。

您可以通过使用std::fpclassify来检查返回值是否表示零来解决这个问题。但对于每个有限(非零)值,您都必须使用epsilon-comparison,除非您在一个编译单元中并且不对值执行任何操作。


嗯,我在这里学到了新东西 =) 我一直以为浮点数比较基本上是按字节比较,但显然我错了。 - example

1

有一个普遍的误解认为浮点数“不精确”。实际上,除了一些特殊情况(如-0.0或Inf),每个浮点数都是完全精确且等于 ·2e – (p – 1),其中 ep分别是尾数、指数和精度,它们都是整数。例如,在IEEE 754-2008二进制32格式(也称为float32)中,p = 24,而1表示为0x‭800000‬‬·20 – 23。当你处理浮点数时,有两件事情确实不准确:

  1. 使用FP值表示实数。显然,并非所有实数都能用给定的FP格式表示,因此它们必须以某种方式四舍五入。有几种舍入模式,但最常用的是“最接近偶数”。如果您始终使用相同的舍入模式(这几乎肯定是情况),则相同的实数始终用相同的FP值表示。因此,如果两个实数相等,则它们的FP对应物也完全相等(但反之不然)。
  2. 使用FP数字的运算(大多数情况下)是不精确的。因此,如果您在计算机中实现了一些实值函数φ(ξ)作为FP参数f(x)的函数,并且您想将其结果与某个“真实”值y进行比较,则需要在比较中使用一些ε,因为很难(有时甚至是不可能的)编写一个给出完全y的函数。而ε的值强烈取决于涉及的FP操作的性质,因此在每种特定情况下可能存在不同的最佳值。

更多细节请参见D. Goldberg的文章《计算机科学家应该了解的浮点运算知识》和J.-M. Muller等人的《浮点运算手册》。这两个文本都可以在互联网上找到。


1

在这两种情况下,您都在使用相同的常量,并将其输入到同一个编译器中。编译器使用的字符串转换为浮点数应该返回相同的位模式,因此它们不仅在加或减零的情况下相等,而且按位相等。

如果您有一个使用操作系统C库生成位模式的常量,然后有一个可能使用不同C库的字符串转换为f或其他内容,如果将二进制文件传输到另一台计算机,则可能会遇到问题。

当然,如果您对其中一个术语(运行时)进行3.3的计算,并再次在编译时计算另一个3.3,则可以并且会在相等比较上失败。显然,有些常量比其他常量更容易工作。

当然,如您所写的,您的3.3比较是无效代码,如果启用了优化,编译器会将其删除。

您没有指定感兴趣的浮点格式或标准(如果有)。例如,某些格式具有+/-零问题,而某些则没有。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接