在大多数情况下,我理解应该使用一系列值(abs(x-y) 我的猜测是nan / inf可能是特殊情况,但我对简单值发生的事情更感兴趣。 修改 如果有人可以引用参考(IEEE浮点标准),我很乐意选择答案吗?// can the assertion be triggered?
float x = //?;
assert( x-x == 0 )
答案 0 :(得分:58)
正如您所暗示的那样,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段):
当两个操作数的总和时 相反的迹象(或相反的迹象) 两个带有相似标志的操作数) 正好零,这个总和的符号(或 差异)总共为+0 舍入方向属性除外 roundTowardNegative;在那之下 属性,精确零的符号 和(或差异)应为-0。
因此x - x
的结果必须为+/- 0
,因此必须等于0.0
(第5.11节,第2段):
比较应忽略零的符号。
进一步编辑:这并不是说错误的编译器不会导致该断言触发。你的问题含糊不清;没有有限的浮点数x
,因此x - x == 0
为假。但是,这不是您发布的代码检查的内容;它检查C风格语言中的某个表达式是否可以计算为非零值;特别是在某些平台上,某些(错误构思的)编译器优化,该表达式中变量x
的两个实例可能具有不同的值,导致断言失败(尤其是如果x
是某些计算的结果,而不是常量,可表示的值)。这是这些平台上的数字模型中的一个错误,但这并不意味着它不可能发生。
答案 1 :(得分:4)
如果表示被转换(例如从x86上的64位内存格式转换为80位内部寄存器格式),我希望在某些情况下可能会触发断言。
答案 2 :(得分:3)
是的,除了特殊情况x-x
将始终为0.但x*(1/x)
并不总是1; - )
答案 3 :(得分:3)
是的,除特殊情况外,自我减法应始终为零。
在调整指数和尾数的比较之前进行加,减,乘或除的问题。当指数相同时,将减去尾数,如果它们相同,则一切都以零结束。
答案 4 :(得分:3)
我对主要问题的回答是:“是否存在x的浮点值,x-x == 0是否为假?”是:至少在英特尔处理器上的浮点实现使“+”和“ - ”操作中的 NO 算术下溢,因此您将无法找到x-x == 0为false的x。对于支持IEEE 754-2008的所有处理器也是如此(参见下面的参考资料)。
我对你的另一个问题的简短回答:如果(xy == 0)完全如此安全(x == y),那么断言(xx == 0)就可以了,因为没有算术下溢会在xx或(xy)中生成。
原因如下。浮点数/双数将以尾数和二进制指数的形式保存在内存中。在标准情况下,尾数被归一化:它是> = 0.5并且&lt; 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下的数字进行算术运算,则此数字将 NOT 标准化,因此您可以使用此数字,例如使用整数(仅使用尾数操作)而不使用任何“舍入”错误”。
备注:我个人尝试不使用“圆错误”字样,因为算术计算机操作中存在无错误。这些操作与+, - ,*和/具有相同计算机编号(如浮点数)的操作不同。浮点数的子集上有确定性操作,可以以形式(尾数,指数)保存,每个都有明确定义的位数。我们可以将这种浮点数子集命名为计算机浮点数。因此经典浮点运算的结果将投影回到计算机浮点数集。这种投影操作是确定性的,并且具有许多特征,例如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)。这个数字实际上是我们可以保存为double的最小数字。数字少于DBL_MIN的所有算术运算与整数乘以2 ^( - 1072)一样。
因此,在使用英特尔处理器的Windows 7计算机上出现无下溢问题。 如果有人有另一个处理器,那么比较我们的结果会很有意思。
有人知道如何用 - 或+操作产生算术下溢吗?我的实验看起来是这样,这是不可能的。
已编辑:我对代码进行了一些修改,以提高代码和消息的可读性。
添加链接:我的实验表明,http://grouper.ieee.org/groups/754/faq.html#underflow在我的英特尔酷睿2 CPU上绝对正确。它的计算方式是在“+”和“ - ”浮点运算中产生 no underflow 。我的结果独立于Strict(/ fp:strict)或Precise(/ fp:precise)Microsoft Visual C编译器开关(请参阅http://msdn.microsoft.com/en-us/library/e7s85ffb%28VS.80%29.aspx和http://msdn.microsoft.com/en-us/library/Aa289157)
一个(可能是最后一个)链接和我最后的评论:我找到了一个很好的参考http://en.wikipedia.org/wiki/Subnormal_numbers,其描述与我之前写的相同。包括非正规数或非规范化数(现在通常称为次正规数,例如In IEEE 754-2008),请遵循以下陈述:
“非正规数字提供了 保证添加和 减去浮点数 永远不会下流;两个在附近 浮点数总是有一个 可表示的非零差异。 没有逐渐下溢, 减法a-b可以下溢和 即使是值,也会产生零 不相等。“
所有我的结果必须才能在任何支持IEEE 754-2008的处理器上正确。
答案 5 :(得分:1)
关于马克所说的内容 - 请查看此链接http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.18。 (不确定它是否适用于您的情况。)