是否存在x的浮点值,x-x == 0是否为假?

时间:2010-04-21 21:19:02

标签: floating-point floating-accuracy ieee-754

在大多数情况下,我理解应该使用一系列值(abs(x-y)

// can the assertion be triggered?
float x = //?;
assert( x-x == 0 )

我的猜测是nan / inf可能是特殊情况,但我对简单值发生的事情更感兴趣。

修改

如果有人可以引用参考(IEEE浮点标准),我很乐意选择答案吗?

6 个答案:

答案 0 :(得分:58)

正如您所暗示的那样,inf - infNaN,不等于零。同样,NaN - NaNNaN。但是,对于任何有限浮点数xx - 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)

是的,除特殊情况外,自我减法应始终为零。

在调整指数和尾数的比较之前进行加,减,乘或除的问题。当指数相同时,将减去尾数,如果它们相同,则一切都以零结束。

http://grouper.ieee.org/groups/754/

答案 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.aspxhttp://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。 (不确定它是否适用于您的情况。)