就在今天,我遇到了我们正在使用的第三方软件,在他们的示例代码中,有这样的内容:
// Defined in somewhere.h
static const double BAR = 3.14;
// Code elsewhere.cpp
void foo(double d)
{
if (d == BAR)
...
}
我知道浮点及其表示的问题,但它让我想知道是否有float == float
会好的情况?我不是在询问可以工作的时间,但是当它有意义并且有效时。
另外,像foo(BAR)
之类的电话呢?它总是比较相同,因为它们都使用相同的static const BAR
?
答案 0 :(得分:37)
是的,保证整数,包括0.0,与==
比较当然,你必须要小心一点,如何获得整数,分配是安全的,但任何计算的结果都是可疑的
ps有一组实数具有完美的再现作为浮点数(想想1 / 2,1 / 4 1/8等)但你可能事先并不知道你有其中一个
只是澄清一下。 IEEE 754保证在范围内浮点表示整数(整数)是精确的。
float a=1.0;
float b=1.0;
a==b // true
但你必须要小心如何得到整数
float a=1.0/3.0;
a*3.0 == 1.0 // not true !!
答案 1 :(得分:34)
有两种方法可以回答这个问题:
float == float
给出正确结果的情况?float == float
可接受编码的情况?(1)的答案是:是的,有时候。但它会变得脆弱,这导致了(2)的答案:不。不要这样做。你将来会乞求奇怪的错误。
对于foo(BAR)
形式的调用:在该特定情况下,比较将返回true,但是当您编写foo
时,您不知道(并且不应该依赖)它被称为。例如,调用foo(BAR)
会很好,但foo(BAR * 2.0 / 2.0)
(甚至可能是foo(BAR * 1.0)
,具体取决于编译器优化的东西)将会中断。你不应该依赖调用者不执行任何算术!
长话短说,尽管a == b
在某些情况下会起作用,但你真的不应该依赖它。即使你今天可以保证调用语义,也许你下周就无法保证它们,所以要省去一些痛苦,不要使用==
。
在我看来,float == float
永远不会*好,因为它几乎无法维护。
*适用于从不小的值。
答案 2 :(得分:14)
其他答案很好地解释了为什么将==
用于浮点数是危险的。我相信,我刚刚发现了一个很好地说明这些危险的例子。
在x86平台上,由于您执行的计算固有的舍入问题,您可以获得某些计算的奇怪浮点结果,这些结果 not 。这个简单的C程序有时会打印“错误”:
#include <stdio.h>
void test(double x, double y)
{
const double y2 = x + 1.0;
if (y != y2)
printf("error\n");
}
void main()
{
const double x = .012;
const double y = x + 1.0;
test(x, y);
}
该程序基本上只计算
x = 0.012 + 1.0;
y = 0.012 + 1.0;
(仅分布在两个函数和中间变量上),但比较仍然会产生错误!
原因是在x86平台上,程序通常使用x87 FPU进行浮点计算。 x87内部计算的精度高于常规double
,因此double
值在存储在内存中时需要舍入。这意味着往返x87 - &gt; RAM - &gt; x87失去精度,因此计算结果根据中间结果是否通过RAM传递或者它们是否都保留在FPU寄存器中而有所不同。这当然是编译器的决定,所以这个bug只能用于某些编译器和优化设置: - (。
有关详细信息,请参阅GCC错误:http://gcc.gnu.org/bugzilla/show_bug.cgi?id=323
相当可怕......
附加说明:
这种类型的错误通常很难调试,因为不同的值一旦达到RAM就会变得相同。
因此,例如,如果您将上述程序扩展为在比较它们后立即打印出y
和y2
的位模式,您将获得完全相同的值 。要打印该值,必须将其加载到RAM中以传递给某些打印函数,如printf
,这将使差异消失...
答案 3 :(得分:8)
但简短的回答是:“不,不要使用==。”
具有讽刺意味的是,当在格式范围内的整数值上操作时,浮点格式“完美地”工作,即具有精确的精度。这意味着如果你坚持使用 double 值,你可以获得略高于50位的完美整数,给你大约+ - 4,500,000,000,000,000,或4.5千万亿。事实上,这就是JavaScript内部工作的原因,这就是为什么JavaScript可以在真正大数字上执行+
和-
之类的操作,但只能<<
和{{1}在32位的。
严格地说,您可以使用精确的表示来精确地比较数字的总和和乘积。那些将是所有整数,加上由 1/2 n 术语组成的分数。因此,通过 n + 0.25,n + 0.50,或 n + 0.75 递增的循环将是正常的,但不是任何其他具有2位数的96个小数部分。< / p>
所以答案是:虽然在狭义的情况下,理论上的确切平等可以理解,但最好避免。
答案 4 :(得分:7)
我使用==
(或!=
)浮动的唯一情况如下:
if (x != x)
{
// Here x is guaranteed to be Not a Number
}
我必须承认我使用Not A Number作为魔术浮点常量(在C ++中使用numeric_limits<double>::quiet_NaN()
)。
对于严格相等的浮点数进行比较没有意义。浮点数的设计具有可预测的相对精度限制。 你负责了解对它们和算法的期望精度。
答案 5 :(得分:7)
我将尝试为浮动平等提供合法,有意义和有用的测试的实际示例。
#include <stdio.h>
#include <math.h>
/* let's try to numerically solve a simple equation F(x)=0 */
double F(double x) {
return 2*cos(x) - pow(1.2, x);
}
/* I'll use a well-known, simple&slow but extremely smart method to do this */
double bisection(double range_start, double range_end) {
double a = range_start;
double d = range_end - range_start;
int counter = 0;
while(a != a+d) // <-- WHOA!!
{
d /= 2.0;
if(F(a)*F(a+d) > 0) /* test for same sign */
a = a+d;
++counter;
}
printf("%d iterations done\n", counter);
return a;
}
int main() {
/* we must be sure that the root can be found in [0.0, 2.0] */
printf("F(0.0)=%.17f, F(2.0)=%.17f\n", F(0.0), F(2.0));
double x = bisection(0.0, 2.0);
printf("the root is near %.17f, F(%.17f)=%.17f\n", x, x, F(x));
}
我宁愿不解释bisection method使用过的本身,而是强调停止条件。它具有完全讨论的形式:(a == a+d)
其中双方都是浮点数:a
是我们当前近似的方程根,d
是我们当前的精度。鉴于算法的前提条件 - 必须是range_start
和range_end
之间的根 - 我们保证在根之间的每次迭代之间a
和a+d
,而d
在每一步都减半,缩小范围。
然后,经过多次迭代后,d
变得如此之小,在添加a
期间,它会四舍五入为零!也就是说,a+d
原来更接近到a
然后再到任何其他浮动;因此FPU将其舍入到最接近的值:到a
本身。这可以通过在假设的计算机上计算来容易地说明;让它有4位十进制尾数和一些大的指数范围。那么机器应该给2.131e+02 + 7.000e-3
的结果是什么?确切的答案是213.107
,但我们的机器不能代表这样的数字;它必须围绕它。并且213.107
更接近213.1
而不是213.2
- 所以舍入的结果变为2.131e+02
- 小的加数消失,四舍五入为零。在我们的算法的某个迭代中完全相同的保证 - 并且在那时我们不能再继续了。我们找到了最大可能精度的根。
一段时间后重新回答答案,我也注意到一个有趣的事实:在上面的算法中,一个不能在停止条件下实际使用“some small number”。对于任何数字的选择,会有输入会认为您的选择太大,导致精度损失,和会有输入会认为您的选择太小,导致过多的迭代甚至进入无限循环。详细讨论如下。
你可能已经知道微积分没有“小数字”的概念:对于任何实数,你可以很容易地找到无数甚至更小的数。问题在于,其中一个“更小”的可能是我们实际寻求的东西;它可能是我们等式的根源。更糟糕的是,对于不同的方程式,可能存在不同的根(例如2.51e-8
和1.38e-8
),两者将由近似如果我们的停止条件看起来像d < 1e-6
,则相同的数字。无论你选择哪个“小数字”,很多根据a == a+d
停止条件被正确找到的根将被“epsilon”太大所破坏。
然而,在浮点数中,指数具有有限的范围,因此您实际上可以找到最小的非零正FP数(例如1e-45
denorm for IEEE 754 single precision FP) 。但它没用! while (d < 1e-45) {...}
将永远循环,假设是单精度(正非零)d
。
撇开那些病态边缘情况,d < eps
停止条件中“小数”的任何选择对于许多方程将太小。在根具有足够高的指数的那些方程中,两个尾数的减法结果仅在最低有效数字处不同将很容易超过我们的“epsilon”。例如,使用6位尾数7.00023e+8 - 7.00022e+8 = 0.00001e+8 = 1.00000e+3 = 1000
,意味着指数+8和5位尾数的数字之间的最小可能差异是...... 1000!例如,1e-4
永远不会适合这种情况。对于具有(相对)高指数的这些数字,我们根本没有足够的精度来看到1e-4
的差异。
我上面的实现也考虑了最后一个问题,你可以看到d
每一步减半,而不是重新计算为(可能是指数中的巨大)a
和{{ {1}}。因此,如果我们将停止条件更改为b
,则算法将不会陷入具有巨大根的无限循环(很可能与d < eps
),但在收缩期间仍将执行不必要的迭代{{ 1}}低于(b-a) < eps
的精度。
这种推理似乎过于理论化和不必要的深刻,但它的目的是再次说明花车的棘手。在围绕它们编写算术运算符时,应该非常小心它们的有限精度。
答案 6 :(得分:4)
如果您在比较它之前永远不会计算该值,那可能没问题。如果您正在测试浮点数是否正好是pi,或-1或1,并且您知道传入的限制值是...
答案 7 :(得分:2)
在将多个算法重写为多线程版本时,我也使用了几次。我使用了一个测试来比较单线程和多线程版本的结果,以确保它们都能给出完全相同的结果。
答案 8 :(得分:2)
在我看来,在大多数情况下需要比较相等(或某些等价):标准C ++容器或带有隐含等式比较算子的算法,例如std :: unordered_set,要求此比较器为等价关系(见 C++ named requirements: UnorderedAssociativeContainer )。
不幸的是,与abs(a - b) < epsilon
中的epsilon相比,它不会产生等价关系,因为它会失去传递性。这很可能是未定义的行为,特别是两个几乎相同的行为。浮点数可以产生不同的哈希值;这可以将unordered_set置于无效状态。
就个人而言,我会在大多数时间使用==浮点数,除非任何操作数都涉及任何类型的FPU计算。对于容器和容器算法,只涉及读/写,==(或任何等价关系)是最安全的。
abs(a - b) < epsilon
或多或少是一个类似于限制的收敛标准。如果我需要验证两次计算之间是否存在数学同一性(例如PV = nRT,或者距离=时间*速度),我发现这种关系很有用。
简而言之,当且仅当没有浮点计算发生时才使用==
;
永远不要使用abs(a-b) < e
作为等式谓词;
答案 9 :(得分:1)
是。除非1/x
,否则x==0
将有效。你不需要在这里进行不精确的测试。 1/0.00000001
完全没问题。我想不出任何其他情况 - 您甚至无法检查tan(x)
<{1}}
答案 10 :(得分:1)
假设您有一个函数可以通过常数因子来缩放浮点数组:
void scale(float factor, float *vector, int extent) {
int i;
for (i = 0; i < extent; ++i) {
vector[i] *= factor;
}
}
我假设您的浮点实现可以精确地表示1.0和0.0,并且0.0由所有0位表示。
如果factor
正好是1.0,则此函数为无操作,您可以在不做任何工作的情况下返回。如果factor
正好是0.0,则可以通过调用memset来实现,这可能比单独执行浮点乘法更快。
reference implementation of BLAS functions at netlib广泛使用此类技术。
答案 11 :(得分:1)
其他帖子显示适当的位置。我认为使用比特精确比较来避免不必要的计算也没问题。
示例:
float someFunction (float argument)
{
// I really want bit-exact comparison here!
if (argument != lastargument)
{
lastargument = argument;
cachedValue = very_expensive_calculation (argument);
}
return cachedValue;
}
答案 12 :(得分:0)
我想说,如果可以接受假阴性答案,那么比较浮点数是否正确 。
假设您有一个程序将浮点值打印到屏幕上,如果浮点值恰好等于M_PI
,那么您希望它打印出来“pi” “相反。如果该值恰好偏离了M_PI
的精确双重表示,它将打印出一个双值,这同样有效,但对用户来说可读性稍差。
答案 13 :(得分:-3)
我有一个绘图程序,从根本上为其坐标系使用浮点,因为允许用户以任何粒度/缩放工作。他们绘制的东西包含可以在它们创建的点处弯曲的线条。当他们将一个点拖到另一个点之上时,它们就会被合并。
为了进行“正确的”浮点比较,我必须提出一些范围来考虑相同的点。由于用户可以放大到无穷大并在该范围内工作,并且由于我无法让任何人承诺某种范围,我们只需使用'=='来查看这些点是否相同。偶尔会出现一个问题,即应该完全相同的点是.000000000001或其他东西(特别是0,0左右),但通常情况下它可以正常工作。如果没有打开快照,它应该很难合并点......或者至少原始版本是如何工作的。
它会偶尔抛出测试组,但那是他们的问题:p
所以无论如何,有一个可能合理的时间使用'=='的例子。需要注意的是,决定不是关于技术准确性而是关于客户希望(或缺乏)和方便。无论如何,这都不需要那么准确。那么,如果两个点在你期望的时候不合并呢?它不是世界末日,也不会影响“计算”。