我对主要问题的回答:“是否存在 x 的浮点值,其中 x-x == 0 为假?”是:至少英特尔处理器上的浮点实现在“+”和“-”操作中使 NO 算术下溢,因此您将无法找到 x-x == 0 为假的 x。 所有支持 IEEE 754-2008 的处理器也是如此(参见下面的参考资料)。
我对另一个问题的简短回答:如果 (xy == 0) 和 (x == y) 一样安全,所以 assert(xx == 0) 是可以的,因为 不会发生算术下溢在 xx 或 (xy) 中产生。
原因如下。浮点数/双精度数将以尾数和二进制指数的形式保存在内存中。在标准情况下,尾数是标准化的:它 >= 0.5 并且 <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标准化,因此您可以像处理整数一样处理这些数字(仅使用尾数进行运算),而无需任何“舍入”错误”。
备注:我个人尽量不要使用“round errors”这个词,因为在计算机算术运算中没有错误。这些操作仅与具有相同计算机数(如浮点数)的 +、-、* 和 / 操作不同。浮点数子集有确定性操作,可以以(尾数、指数)的形式保存,每个浮点数都有明确定义的位数。我们可以将这种浮点子集命名为计算机浮点数。所以经典浮点运算的结果会投影回计算机浮点数集。这种投影操作是确定性的,并且有很多特征,比如 if x1 >= x2 then 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) 一样工作。
所以我在装有 Intel 处理器的 Windows 7 计算机上没有下溢问题。 如果有人有其他处理器,比较我们的结果会很有趣。
有人知道如何使用 - 或 + 操作产生算术下溢吗?我的实验看起来是这样,那是不可能的。
已编辑:我稍微修改了代码以提高代码和消息的可读性。
添加的链接:我的实验表明,http://grouper.ieee.org/groups/754/faq.html#underflow 在我的 Intel Core 2 CPU 上是绝对正确的。它的计算方式在“+”和“-”浮点运算中产生没有下溢。我的结果独立于 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,其中的描述与我之前写的相同。包括非正规数或非正规数(现在通常称为次正规数,例如在 IEEE 754-2008 中)遵循以下声明:
“非正规数提供
保证加法和
浮点数减法
永不下溢;附近有两个
浮点数总是有一个
可表示的非零差异。
在没有逐渐下溢的情况下,
减法 a−b 可以下溢并且
即使值产生零
不相等。”
所以我的所有结果必须在任何支持 IEEE 754-2008 的处理器上都是正确的。