在大多数情况下,我了解到浮点数比较测试应该在一定范围内实现(abs(x-y) < epsilon),但是自减操作是否意味着结果将为零?
// can the assertion be triggered?
float x = //?;
assert( x-x == 0 )
我猜nan/inf可能是特殊情况,但我更感兴趣的是针对简单值会发生什么。
编辑:
如果有人可以引用参考资料(IEEE浮点标准),我很乐意选择一个答案。
在大多数情况下,我了解到浮点数比较测试应该在一定范围内实现(abs(x-y) < epsilon),但是自减操作是否意味着结果将为零?
// can the assertion be triggered?
float x = //?;
assert( x-x == 0 )
我猜nan/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段):
Further Edit: 这并不意味着有错误的编译器不能导致该断言触发。您的问题含糊不清; 不存在任何有限浮点数比较应忽略零的符号。
x
,使得 x - x == 0
为假。然而,这不是您发布的代码所检查的内容。它检查一个C语言风格中的某个表达式是否能够求值为非零值;特别地,在某些平台上,使用某些(考虑不周的)编译器优化,该表达式中的变量x
的两个实例可能具有不同的值,导致断言失败(特别是如果x是某种计算结果,而不是常数可表示的值)。这是那些平台上数字模型中的错误,但是这并不意味着它不可能发生。
x - x
不太可能在实际代码中使用(你为什么要这样做呢?),但是减去(或比较)变量值与可能产生该值的表达式可能会发生,并且由于编译器可能处理中间值的精度而产生意外结果。请参见 https://dev59.com/ZUzSa4cB1Zd3GeqPpLwz,这是一个可能类似于真实世界中可能发生的示例。 - Michael Burr<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的数字时所拥有的内容,我编写了一个小型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"
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)的整数一样进行。“非规格化数确保浮点数的加法和减法永远不会下溢;两个相邻的浮点数总是有一个可表示的非零差异。没有逐渐下溢,减法a−b可以下溢并产生零,即使这些值不相等。”
因此,在支持IEEE 754-2008的任何处理器上,我的所有结果必须都是正确的。
是的,除了特殊情况,x-x
总是等于0。但是x*(1/x)
并不总是等于1 ;-)
inf
和NaN
)。 - Chris Lutz