是否有x的浮点值,其中x-x == 0是否为false?
在大多数情况下,我明白浮点比较测试应该使用超过一系列值(abs(x-y)< epsilon)来实现,但是自减是否意味着结果将为零?是否有x的浮点值,其中x-x == 0是否为false?
// can the assertion be triggered?
float x = //?;
assert(x-x == 0)
我的猜测是南/ inf可能是特殊情况,但我更关心简单的值会发生什么。
编辑:
我很高兴来接,如果有人可以举一个参考(IEEE浮点标准)的答案?
如你暗示,inf - inf
是NaN
,这是不等于零。同样,NaN - NaN
是NaN
。但是,对于任何有限浮点数x
,x - x == 0.0
(取决于舍入模式,x - x
的结果可能为负零,但在浮点算法中为负的零比较等于0.0
)确实如此。
编辑:给出一个明确的标准参考是有点棘手的,因为这是IEEE-754标准规定的规则的一个新特性。具体来说,它是从第5条中定义的操作被正确舍入的要求得出的。减法是这样的操作(第5.4.1节“算术运算”),以及x - x
正确的舍入结果是适当的符号(第6.3节,第3段)的零点:
当两个总和与 相反的符号(或 两个操作数中包含的标志的差)操作数是 正好为零,即和的正负号(或 差)应在所有 除了 roundTowardNegative舍入方向属性0;在该 属性下,总和(或差值)的确切零 的符号应为-0。
所以的x - x
结果必然是+/- 0
,因此必须比较等于0.0
(第5.11节,第2段):
比较应忽略零的符号。
进一步编辑:这并不是说,一个马车编译器不能导致该断言火。你的问题不明确;没有有限浮点数x
,因此x - x == 0
是错误的。但是,这不是您发布的代码所检查的内容;它检查C样式语言中的某个表达式是否可以评估为非零值;特别是,在某些平台上,具有一定的(考虑不周的)编译器优化中,可变x
在该表达式中的两个实例可能具有不同值,导致断言失败(特别是如果x
是一些计算的结果,而不是一个常量,可表示的值)。这是这些平台上数值模型中的一个错误,但这并不意味着它不会发生。
太棒了,正是我在找的 – 2010-04-21 21:43:17
+1。 Whodathunk互联网可以是这个确切的。 (不要从你身上取任何东西,史蒂芬。) – Potatoswatter 2010-04-21 21:44:19
@Patatoswatter:它有助于花了几个月的时间作为754草案标准的编辑。没有那个背景,我不知道该去哪里寻找这些东西。 – 2010-04-21 21:49:05
是,除了特殊情况x-x
将始终为0,但x*(1/x)
不会永远是1 ;-)
他不是要求特例吗? – 2010-04-21 21:24:43
@Frank - 是的,但他忽略了ypnos指的两个特殊情况('inf'和'NaN')。 – 2010-04-21 21:33:39
是,自减法应该总是导致零,特殊情况除外。
问题发生在您对指数和尾数进行调整的比较之前进行加,减,乘或除。当指数相同时,扣除尾数,如果它们相同,则一切都结束为零。
如果表示被变换(例如,从64位的存储器格式在x86 80位内部寄存器格式)我期望断言可能可能火在某些情况下。
由于问题的措辞,这种情况可能是不可能的。但是'x = a + b; assert(x-(a + b)== 0)'可能触发它。 – 2010-04-21 21:34:13
我认为这是一个需要关注的关键问题 - “x-x”表达式不太可能用于实际代码中(为什么会这样?),但是将变量的值与表达式已经产生了可能发生的值,并且由于编译器如何处理中间值的精度,可能会产生意想不到的结果。请参阅http://stackoverflow.com/questions/2497825/gcc-problem-with-raw-double-type-comparisons,这个例子可能与现实世界中可能发生的事情不同。 – 2010-04-21 22:03:19
我对主要问题的答案:“有x的浮点值,其中X-X == 0是假的”是:至少在英特尔处理器上执行浮点运算会使“+”和“ - ”运算中的算术下溢,因此您将无法找到x-x == 0为假的x。所有支持IEEE 754-2008的处理器(请参见下文参考资料)均为。
对另一个问题的简短回答:if(xy == 0)与if(x == y)完全一样安全,所以assert(xx == 0)是OK,因为没有算术下溢在xx或(xy)中产生。
原因如下。浮点数/双精度数将以尾数和二进制指数形式存储在内存中。在标准情况下,尾数是标准化的:> = 0.5和< 1.在<float.h>
中,您可以从IEEE浮点标准中找到一些常量。现在有趣的我们只以下
#define DBL_MIN 2.2250738585072014e-308 /* min positive value */
#define DBL_MIN_10_EXP (-307) /* min decimal exponent */
#define DBL_MIN_EXP (-1021) /* min binary exponent */
但并不是每个人都知道,你可以有双号小于 DBL_MIN。如果你做与DBL_MIN下数算术运算,这个数字将是不标准化,所以你这个数字就像用整数(操作只有尾数)没有任何“轮错误”。
备注:我个人尽量不要用言语“轮错误”,因为有在算术计算机操作没有错误。这些操作只与具有相同计算机号码(例如浮点数)的+, - ,*和/操作不同。在浮点数的子集上存在确定性操作,其可以以具有明确定义的比特数的形式(尾数,指数)保存。我们可以将这种浮动子集名称为计算机浮动号码。所以的结果经典浮点运算将预计回到电脑浮点数集。这种投影操作是确定性的,并且具有很多特征,如果x1> = x2,则x1 * y> = x2 * y。
对不起,我们再回到主题。
要显示正是我们,如果我们用不到DBL_MIN号码进行操作,我用C写了一个小程序:
#include <stdio.h>
#include <float.h>
#include <math.h>
void DumpDouble(double d)
{
unsigned char *b = (unsigned char *)&d;
int i;
for (i=1; i<=sizeof(d); i++) {
printf ("%02X", b[sizeof(d)-i]);
}
printf ("\n");
}
int main()
{
double x, m, y, z;
int exp;
printf ("DBL_MAX=%.16e\n", DBL_MAX);
printf ("DBL_MAX in binary form: ");
DumpDouble(DBL_MAX);
printf ("DBL_MIN=%.16e\n", DBL_MIN);
printf ("DBL_MIN in binary form: ");
DumpDouble(DBL_MIN);
// Breaks the floating point number x into its binary significand
// (a floating point value between 0.5(included) and 1.0(excluded))
// and an integral exponent for 2
x = DBL_MIN;
m = frexp (x, &exp);
printf ("DBL_MIN has mantissa=%.16e and exponent=%d\n", m, exp);
printf ("mantissa of DBL_MIN in binary form: ");
DumpDouble(m);
// ldexp() returns the resulting floating point value from
// multiplying x (the significand) by 2
// raised to the power of exp (the exponent).
x = ldexp (0.5, DBL_MIN_EXP); // -1021
printf ("the number (x) constructed from mantissa 0.5 and exponent=DBL_MIN_EXP (%d) in binary form: ", DBL_MIN_EXP);
DumpDouble(x);
y = ldexp (0.5000000000000001, DBL_MIN_EXP);
m = frexp (y, &exp);
printf ("the number (y) constructed from mantissa 0.5000000000000001 and exponent=DBL_MIN_EXP (%d) in binary form: ", DBL_MIN_EXP);
DumpDouble(y);
printf ("mantissa of this number saved as double will be displayed by printf(%%.16e) as %.16e and exponent=%d\n", m, exp);
y = ldexp ((1 + DBL_EPSILON)/2, DBL_MIN_EXP);
m = frexp (y, &exp);
printf ("the number (y) constructed from mantissa (1+DBL_EPSILON)/2 and exponent=DBL_MIN_EXP (%d) in binary form: ", DBL_MIN_EXP);
DumpDouble(y);
printf ("mantissa of this number saved as double will be displayed by printf(%%.16e) as %.16e and exponent=%d\n", m, exp);
z = y - x;
m = frexp (z, &exp);
printf ("z=y-x in binary form: ");
DumpDouble(z);
printf ("z will be displayed by printf(%%.16e) as %.16e\n", z);
printf ("z has mantissa=%.16e and exponent=%d\n", m, exp);
if (x == y)
printf ("\"if (x == y)\" say x == y\n");
else
printf ("\"if (x == y)\" say x != y\n");
if ((x-y) == 0)
printf ("\"if ((x-y) == 0)\" say \"(x-y) == 0\"\n");
else
printf ("\"if ((x-y) == 0)\" say \"(x-y) != 0\"\n");
}
由此代码生成以下的输出:
DBL_MAX=1.7976931348623157e+308
DBL_MAX in binary form: 7FEFFFFFFFFFFFFF
DBL_MIN=2.2250738585072014e-308
DBL_MIN in binary form: 0010000000000000
DBL_MIN has mantissa=5.0000000000000000e-001 and exponent=-1021
mantissa of DBL_MIN in binary form: 3FE0000000000000
the number (x) constructed from mantissa 0.5 and exponent=DBL_MIN_EXP (-1021) in binary form: 0010000000000000
the number (y) constructed from mantissa 0.5000000000000001 and exponent=DBL_MIN_EXP (-1021) in binary form: 0010000000000001
mantissa of this number saved as double will be displayed by printf(%.16e) as 5.0000000000000011e-001 and exponent=-1021
the number (y) constructed from mantissa (1+DBL_EPSILON)/2 and exponent=DBL_MIN_EXP (-1021) in binary form: 0010000000000001
mantissa of this number saved as double will be displayed by printf(%.16e) as 5.0000000000000011e-001 and exponent=-1021
z=y-x in binary form: 0000000000000001
z will be displayed by printf(%.16e) as 4.9406564584124654e-324
z has mantissa=5.0000000000000000e-001 and exponent=-1073
"if (x == y)" say x != y
"if ((x-y) == 0)" say "(x-y) != 0"
所以我们可以看到,如果我们使用小于DBL_MIN的数字,它们将不会被标准化(请参阅0000000000000001
)。我们正在使用整数这些数字,并没有任何“错误”。因此,如果我们分配y=x
然后if (x-y == 0)
正是如此安全为if (x == y)
和assert(x-x == 0)
工程确定。在这个例子中,z = 0.5 * 2 ^( - 1073)= 1 * 2 ^( - 1072)。这个数字实际上是我们可以节省一倍的最小数字。所有数字少于DBL_MIN的算术运算就像整数乘以2 ^( - 1072)。
所以我有没有下溢问题在我的Windows 7计算机与英特尔处理器。 如果有人有另一个处理器,比较我们的结果会很有趣。
有人有一个想法,一个人如何能产生算术下溢 - 或+操作?我的实验看起来像这样,这是不可能的。
EDITED:为了更好地读取代码和消息,我稍微修改了代码。
添加的链接:我的实验显示,http://grouper.ieee.org/groups/754/faq.html#underflow在我的英特尔酷睿2 CPU上绝对正确。在“+”和“ - ”浮点运算中,将如何计算产生的下溢。我的结果是独立的严格的(/ FP:严格)或精确(/ FP:精确)的Microsoft Visual C编译器开关(见http://msdn.microsoft.com/en-us/library/e7s85ffb%28VS.80%29.aspx和http://msdn.microsoft.com/en-us/library/Aa289157)
ONE MORE(可能是最后一个)LINK AND MY FINAL REMARK :我发现了一个很好的参考文献http://en.wikipedia.org/wiki/Subnormal_numbers,其中描述的是我之前写的。包括非正规数字或非正规数的(现在通常称为例如低于正常的数量在IEEE 754-2008)遵循以下statment:
“非正规号码提供 保证浮点数的加法和 减法 从未溢;两个附近的 浮点数总是有一个 可表示的非零差异。 没有渐进下溢,在 减法A-B可以下溢和 产生零即使值 是不相等的。”
所以我所有的结果必须是在其支持IEEE 754-任何处理器正确2008年。
您已接受的问题,但也请阅读我的答案http://stackoverflow.com/questions/2686644/is-there-a-floating-point-value-of-x-for-which-xx-0 -is假/ 2687323#2687323。它可以清除(我希望)并关闭你的问题。 – Oleg 2010-04-22 11:42:56