提问人:463035818_is_not_an_ai 提问时间:8/24/2016 最后编辑:Clifford463035818_is_not_an_ai 更新时间:4/6/2019 访问量:11642
是否可以在没有 epsilon 的情况下将浮点数与 0.0 进行比较?
Is it ok to compare floating points to 0.0 without epsilon?
问:
我知道,要比较两个浮点值,需要使用一些 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
或者更一般地说,下面的比较是否总是正确的?
double x = 3.3;
double y = 3.3;
if (x == y) { std::cout << "is an epsilon required here?" << std::endl; }
当我尝试它时,它似乎有效,但可能人们不应该依赖它。
答:
是的,如果你回来,你可以把它比作;0 可以精确地表示为浮点值。如果你返回,你必须更加小心,因为它不能完全表示,所以例如,从 double 到 float 的转换将产生不同的值。0.0
0.0
3.3
3.3
评论
是的,在此示例中,检查 是完全可以的。这并不是因为在任何方面都很特别,而是因为你只分配一个值,然后进行比较。您也可以将其设置为并比较,这也很好。您正在存储一个位模式,并比较完全相同的位模式,只要这些值没有被提升为另一种类型来进行比较。== 0.0
0.0
3.3
== 3.3
但是,在数学上等于零的计算结果并不总是等于 。0.0
此问答已发展到还包括由不同编译器编译程序的不同部分的情况。这个问题没有提到这一点,我的答案仅适用于所有相关部分使用相同的编译器时。
C++ 11 标准,
§5.10 相等运算符
6 如果两个操作数都是算术或枚举类型,则通常 在两个操作数上执行算术转换;每个 如果指定的关系为 true,则运算符应产生 true,并且 如果为 false,则为 false。
这种关系没有进一步定义,因此我们必须使用“平等”的共同含义。
§2.13.4 浮动文字
1 [...]如果缩放值在可表示值的范围内 对于其类型,结果是缩放值(如果可表示),否则 最接近缩放值的更大或更小的可表示值, 以实现定义的方式选择。[...]
当值不可表示时,编译器在转换文本时必须在两个值之间进行选择。如果始终如一地为同一文本选择相同的值,则可以安全地比较诸如 等值,因为表示“相等”。3.3
==
评论
0.0
0.0
y = foo(x); z = 0.0; if (memcmp(&y, &z, sizeof(z)) == 0) { it_is_zero(); }
更正:因为浮点值不是唯一的,但 IEEE 754 将比较定义为 true(任何零)。0
0.0==-0.0
因此,对于其他所有数字,它都不起作用。一个编译单元(例如库)和另一个编译单元(例如您的应用程序)中的文本可能不同。该标准仅要求编译器使用与运行时相同的舍入,但不同的编译器/编译器设置可能使用不同的舍入。0.0
3.3
它大部分时间都会起作用(对于 0
),但这是非常糟糕的做法。
只要您使用具有相同设置(例如一个编译单元)的相同编译器,它就会起作用,因为文字 or 每次都会转换为相同的位模式。不过,零的表示并不是唯一的。因此,如果在库中声明,并且在某些应用程序中调用它,则同一函数可能会失败。0.0
0.0f
foo
您可以通过使用 std::fpclassify
检查返回的值是否表示零来挽救这种情况。对于每个有限(非零)值,除非您停留在一个编译单元内并且不对值执行任何操作,否则您将不得不使用 epsilon-comparison。
评论
正如在这两种情况下所写的,您在提供给同一编译器的同一文件中使用相同的常量。编译器使用的字符串到浮点数转换应返回相同的位模式,因此这些位模式不仅应该像零事物的正负情况那样相等,而且应该逐位相等。
如果你有一个常量,它使用操作系统 C 库来生成位模式,那么有一个字符串到 f 或如果二进制文件被传输到另一台计算机而不是编译的计算机,它可能会使用不同的 C 库。您可能有问题。
当然,如果您为其中一个术语 Runtime 计算 3.3,并再次计算另一个 3.3 编译时间,则可以并且将在相等的比较中失败。某些常量显然比其他常量更有可能起作用。
当然,正如所写的那样,你的 3.3 比较是死代码,如果启用了优化,编译器就会删除它。
您没有指定浮点格式,也没有指定您感兴趣的格式的标准(如果有)。例如,有些格式有 +/- 零问题,有些则没有。
一个常见的误解是浮点值“不准确”。事实上,它们中的每一个都是完全精确的(除了一些特殊情况,如 -0.0 或 Inf)并且等于 s·2 e –(p – 1),其中 s、e 和 p 分别是有效、指数和精度,它们中的每一个都是整数。E.g. in IEEE 754-2008 binary32 format (aka float32) p = 24 and 1 is represented as 0x800000·20 – 23.在处理浮点值时,有两件事确实不准确:
- 使用 FP 表示实际值。显然,并非所有实数都可以使用给定的 FP 格式表示,因此必须以某种方式对它们进行舍入。有几种舍入模式,但最常用的是“舍入到最近,并列到偶数”。如果您始终使用相同的舍入模式(几乎可以肯定是这种情况),则始终使用相同的 FP 表示相同的实际值。因此,您可以确定,如果两个实数值相等,则它们的 FP 对应项也完全相等(但显然不是相反)。
- FP 编号的操作(大部分)不准确。因此,如果你在计算机中实现了一些实值函数 φ(ξ) 作为 FP 参数 f(x) 的函数,并且你想将其结果与某个“真实”值 y 进行比较,你需要使用一些ε进行比较,因为要精确地给出 y 的函数是非常困难的(有时甚至是不可能的)。ε的值很大程度上取决于所涉及的 FP 操作的性质,因此在每种特定情况下,可能有不同的最佳值。
有关详细信息,请参阅 D. Goldberg。每个计算机科学家都应该了解的关于浮点运算和 J.-M.Muller等人。浮点运算手册。你可以在互联网上找到这两个文本。
评论
0.0
3.3
3.3