float 与 float 文字相比的奇怪输出

strange output in comparison of float with float literal

提问人:Ashish 提问时间:12/3/2009 最后编辑:Bo PerssonAshish 更新时间:12/4/2022 访问量:10693

问:

float f = 0.7;
if( f == 0.7 )
    printf("equal");
else
    printf("not equal");

为什么输出?not equal

为什么会这样?

C++ C 浮点双 精度

评论

0赞 int3 12/3/2009
例如,参见 stackoverflow.com/questions/1839364/float-addition-issuestackoverflow.com/questions/1839225/...
15赞 pmg 12/3/2009
阅读“每个计算机科学家都应该知道的关于浮点运算的知识” [ docs.sun.com/source/806-3568/ncg_goldberg.html ]
0赞 Charles Salvia 12/3/2009
哇,这是关于浮点精度的连续第三个 C/C++ 问题。请参阅 stackoverflow.com/questions/1839364/float-addition-issuestackoverflow.com/questions/1839225/...
0赞 UncleBens 12/3/2009
每个人都看到了第一个问题,并开始尝试他们还能得到什么其他奇怪的东西?
3赞 paxdiablo 11/22/2018
0.7不是“浮点文字”,而是一个 .double

答:

56赞 halfdan 12/3/2009 #1

发生这种情况是因为在您的陈述中

  if(f == 0.7)

0.7 被视为双精度。尝试 0.7f 以确保将该值视为浮点数:

  if(f == 0.7f)

但正如 Michael 在下面的评论中建议的那样,你永远不应该测试浮点值的完全相等性。

评论

3赞 Timbo 12/3/2009
是的。f 后缀(如 0.7f 中)使它们成为浮点数文字。
18赞 Michael Carman 12/3/2009
也许更重要的是,不要测试浮点值的完全相等性。
0赞 Ed S. 12/5/2009
是的,迈克尔在这里提出了最好的答案。你永远不应该寻找浮点数之间的相等性。这个答案只会导致以后更多的混乱。
16赞 Pascal Cuoq 3/5/2013
“不要测试浮点值的相等性”只是基于迷信。整数溢出是有原因的。作为一个程序员,人们不会放弃理性的道路,并宣称“试图用整数计算是没有用的,它们会溢出”。人们学习整数是如何工作的以及如何正确使用它们。浮点也是如此。浮点计算可能是精确的,也可能不是,这是有原因的。原因不难理解。在某些情况下,测试相等性是有意义的。
14赞 Pascal Cuoq 12/3/2009 #2

这个答案是对现有答案的补充:请注意,0.7 不能完全表示为浮点数(或双精度)。如果它被精确地表示,那么在转换为浮点数然后返回双精度时不会丢失信息,您就不会有这个问题。

甚至可以说,对于无法精确表示的文字浮点常量,应该有一个编译器警告,特别是当标准对于舍入是在运行时以设置为该时间的模式还是在编译时以另一种舍入模式进行时非常模糊时。

所有可以精确表示的非整数都具有最后一个十进制数字。不幸的是,反之亦然:有些数字的最后一个十进制数字是无法准确表示的。小整数都可以精确表示,除以 2 的幂会将一个可以表示的数字转换为另一个可以表示的数字,只要您不进入非规范化数字的领域。55

评论

1赞 Mark Ransom 8/15/2012
这里需要定义“小整数”——IEEE 浮点数将容纳 24 位而不会丢失,或者最多 16777215。双保持 54 位,比标准 int 大得多。
0赞 Stephen Canon 3/5/2013
@MarkRansom:对于数学家来说,所有小于任何固定边界的整数都是“小”的。此外,53 位。
0赞 Mark Ransom 3/5/2013
@StephenCanon,感谢您的更正,我知道它是 53 位,所以我恳求暂时的精神错乱。
6赞 Floris 6/21/2013
“所有可以精确表示的非整数都以 5 作为其最后小数点。”每天我都会学到新东西。今天就是这样。谢谢!
0赞 Omnifarious 12/30/2018
@Floris - 任何可以转换为以 2 的幂为分母的分数的小数都可以精确表示。所有这些分数都将以 5 结尾,因为小数点中的分母将是 10 的幂,您必须将 10 除以 5 才能得到 2,因此分子必须至少能被 5 整除一次。
1赞 Kevin Mack 9/21/2015 #3

正如其他评论者所指出的那样,您面临的问题是,测试浮点数之间的精确等价通常是不安全的,因为计算中的初始化错误或舍入错误可能会引入细微的差异,从而导致 == 运算符返回 false。

更好的做法是做类似的事情

float f = 0.7;
if( fabs(f - 0.7) < FLT_EPSILON )
    printf("equal");
else
    printf("not equal");

假设FLT_EPSILON已定义为平台的适当小浮点值。

由于舍入或初始化误差不太可能超过 FLT_EPSILON 的值,因此这将为您提供所需的可靠等效性检验。

0赞 Setu Kumar Basak 2/5/2016 #4

考虑一下:

int main()
{
    float a = 0.7;
    if(0.7 > a)
        printf("Hi\n");
    else
        printf("Hello\n");
    return 0;
}

如果 (0.7 > a),这里 a 是一个浮点变量,是一个双精度常数。double 常数大于浮点变量 a。因此,满足 if 条件并打印0.70.7'Hi'

例:

int main()
{
    float a=0.7;
    printf("%.10f %.10f\n",0.7, a);
    return 0;
}

输出:
0.7000000000 0.6999999881

14赞 Alexandr 7/12/2016 #5

首先,让我们看看浮点数的内部。我取 0.1f 它是 4 字节长 (binary32),十六进制是
3D CC CC CD
通过标准IEEE 754将其转换为十进制,我们必须这样做:

enter image description here
在二进制 3D CC CC CD 中
01111011 1001100 为 0 11001100 11001101
这里的第一个数字是符号位。0 表示 (-1)^0 表示我们的数字为正。
第二个 8 位是指数。在二进制中,它是01111011 - 十进制 123。但是真正的指数是 123-127(总是 127)=-4,这意味着我们需要将得到的数字乘以 2^ (-4)。
最后 23 个字节是 Significand 精度。在那里,第一位乘以 1/ (2^1) (0.5),第二位乘以 1/ (2^2) (0.25),依此类推。在这里我们得到:


enter image description here enter image description here

我们需要将所有数字(2 的幂)相加并加到 1(始终为 1,通过标准)。它是
1,60000002384185791015625
现在让我们将这个数字乘以 2^ (-4),它来自指数。我们只是将上面的数字减去 2 四次:
0,100000001490116119384765625
我使用了 MS 计算器



**

现在是第二部分。从十进制转换为二进制。

**
我拿数字 0.1
它很容易,因为没有整数部分。第一个符号位 - 它是 0。 指数和有效精度我现在将计算。逻辑乘以 2 整数 (0.1*2=0.2),如果它大于 1,则减去并继续。
enter image description here
这个数字是 .00011001100110011001100110011,标准说我们必须向左移动才能得到 1。(某事)。你怎么看,我们需要 4 个班次,从这个数字计算指数 (127-4=123)。现在的 Significand 精度是
10011001100110011001100(并且有丢失的位)。
现在是整数。符号位 0 指数是 123 (01111011),有效精度是 10011001100110011001100 和整体,它是
00111101110011001100110011001100让我们将其与上一章
00111101110011001100110011001101 中的那些进行比较
如您所见,最后一位不相等。这是因为我截断了这个数字。CPU 和编译器知道 是 Significand 精度之后无法保持的东西,只需将最后一位设置为 1。

评论

0赞 SpawN 1/26/2022
即使 0.5 的尾数适合这种格式,0.5 比较如何不起作用?
1赞 Jimmy Pettersson 12/27/2016 #6

网络上的很多答案都错误地查看浮点数之间的差异,这只适用于特殊情况,可靠的方法是查看相对差异,如下所示:

      // Floating point comparison:

        bool CheckFP32Equal(float referenceValue, float value)
        {
           const float fp32_epsilon = float(1E-7);
           float abs_diff = std::abs(referenceValue - value);

           // Both identical zero is a special case
           if( referenceValue==0.0f && value == 0.0f)
              return true;

           float rel_diff = abs_diff / std::max(std::abs(referenceValue) , std::abs(value) ); 

           if(rel_diff < fp32_epsilon)
                 return true;
           else 
                 return false;

        }
3赞 old_timer 12/30/2018 #7

另一个几乎完全相同的问题与这个问题有关,因此答案晚了几年。我认为上述答案并不完整。

int fun1 ( void )
{
      float x=0.7;
      if(x==0.7) return(1);
      else       return(0);
}
int fun2 ( void )
{
      float x=1.1;
      if(x==1.1) return(1);
      else       return(0);
}
int fun3 ( void )
{
      float x=1.0;
      if(x==1.0) return(1);
      else       return(0);
}
int fun4 ( void )
{
      float x=0.0;
      if(x==0.0) return(1);
      else       return(0);
}
int fun5 ( void )
{
      float x=0.7;
      if(x==0.7f) return(1);
      else       return(0);
}
float fun10 ( void )
{
    return(0.7);
}
double fun11 ( void )
{
    return(0.7);
}
float fun12 ( void )
{
    return(1.0);
}
double fun13 ( void )
{
    return(1.0);
}

Disassembly of section .text:

00000000 <fun1>:
   0:   e3a00000    mov r0, #0
   4:   e12fff1e    bx  lr

00000008 <fun2>:
   8:   e3a00000    mov r0, #0
   c:   e12fff1e    bx  lr

00000010 <fun3>:
  10:   e3a00001    mov r0, #1
  14:   e12fff1e    bx  lr

00000018 <fun4>:
  18:   e3a00001    mov r0, #1
  1c:   e12fff1e    bx  lr

00000020 <fun5>:
  20:   e3a00001    mov r0, #1
  24:   e12fff1e    bx  lr

00000028 <fun10>:
  28:   e59f0000    ldr r0, [pc]    ; 30 <fun10+0x8>
  2c:   e12fff1e    bx  lr
  30:   3f333333    svccc   0x00333333

00000034 <fun11>:
  34:   e28f1004    add r1, pc, #4
  38:   e8910003    ldm r1, {r0, r1}
  3c:   e12fff1e    bx  lr
  40:   66666666    strbtvs r6, [r6], -r6, ror #12
  44:   3fe66666    svccc   0x00e66666

00000048 <fun12>:
  48:   e3a005fe    mov r0, #1065353216 ; 0x3f800000
  4c:   e12fff1e    bx  lr

00000050 <fun13>:
  50:   e3a00000    mov r0, #0
  54:   e59f1000    ldr r1, [pc]    ; 5c <fun13+0xc>
  58:   e12fff1e    bx  lr
  5c:   3ff00000    svccc   0x00f00000  ; IMB

为什么 fun3 和 fun4 返回一个而不是其他的?为什么 fun5 有效?

这是关于语言的。该语言说 0.7 是双精度,除非您使用此语法 0.7f,否则它是单精度。所以

  float x=0.7;

double 0.7 转换为 single 并存储在 x 中。

  if(x==0.7) return(1);

该语言说我们必须提升到更高的精度,因此 x 中的 single 被转换为 double,并与 double 进行比较 0.7。

00000028 <fun10>:
  28:   e59f0000    ldr r0, [pc]    ; 30 <fun10+0x8>
  2c:   e12fff1e    bx  lr
  30:   3f333333    svccc   0x00333333

00000034 <fun11>:
  34:   e28f1004    add r1, pc, #4
  38:   e8910003    ldm r1, {r0, r1}
  3c:   e12fff1e    bx  lr
  40:   66666666    strbtvs r6, [r6], -r6, ror #12
  44:   3fe66666    svccc   0x00e66666

单人 3F333333 双 3fe66666666666666

正如Alexandr所指出的,如果这个答案仍然是IEEE 754,那么一个是

seeeeeeeffffffffffffff

而双倍是

seeeeeeeeeeeffff

具有 52 位分数,而不是单通道的 23 位。

00111111001100110011... single
001111111110011001100110... double

0 01111110 01100110011... single
0 01111111110 01100110011... double

就像以 10 为基数的 1/3 是 0.3333333......永远。我们在这里有一个重复的模式 0110

01100110011001100110011 single, 23 bits
01100110011001100110011001100110.... double 52 bits.

这就是答案。

  if(x==0.7) return(1);

x 包含 01100110011001100110011 作为其分数,当它被转换回来时 将分数加倍是

01100110011001100110011000000000....

这不等于

01100110011001100110011001100110...

但在这里

  if(x==0.7f) return(1);

这种提升不会发生,相同的位模式会相互比较。

为什么 1.0 有效?

00000048 <fun12>:
  48:   e3a005fe    mov r0, #1065353216 ; 0x3f800000
  4c:   e12fff1e    bx  lr

00000050 <fun13>:
  50:   e3a00000    mov r0, #0
  54:   e59f1000    ldr r1, [pc]    ; 5c <fun13+0xc>
  58:   e12fff1e    bx  lr
  5c:   3ff00000    svccc   0x00f00000  ; IMB

0011111110000000...
0011111111110000000...

0 01111111 0000000...
0 01111111111 0000000...

在这两种情况下,分数都是零。因此,从双倍到单倍再到双倍的转换不会损失精度。它准确地从单重命名为双精度,并且两个值的位比较有效。

halfdan投票和检查的最高答案是正确答案,这是一个混合精度的情况,你永远不应该做相等的比较。

为什么没有在那个答案中显示。0.7 失败 1.0 有效。为什么没有显示 0.7 失败。重复的问题 1.1 也会失败。


编辑

平等可以在这里从问题中取出,这是一个已经回答过的不同问题,但它是同一个问题,并且还有“什么......”最初的震惊。

int fun1 ( void )
{
      float x=0.7;
      if(x<0.7) return(1);
      else       return(0);
}
int fun2 ( void )
{
      float x=0.6;
      if(x<0.6) return(1);
      else       return(0);
}

Disassembly of section .text:

00000000 <fun1>:
   0:   e3a00001    mov r0, #1
   4:   e12fff1e    bx  lr

00000008 <fun2>:
   8:   e3a00000    mov r0, #0
   c:   e12fff1e    bx  lr

为什么一个显示小于,另一个显示不小于?当它们应该相等时。

从上面我们知道 0.7 的故事。

01100110011001100110011 single, 23 bits
01100110011001100110011001100110.... double 52 bits.

01100110011001100110011000000000....

小于。

01100110011001100110011001100110...

0.6 是不同的重复模式 0011 而不是 0110。

但是当从双倍转换为单倍时或一般情况下表示 作为单个 IEEE 754。

00110011001100110011001100110011.... double 52 bits.
00110011001100110011001 is NOT the fraction for single
00110011001100110011010 IS the fraction for single

IEEE 754 使用舍入模式、向上舍入、向下舍入或舍入到零。默认情况下,编译器倾向于向上舍入。如果你还记得小学时四舍五入,12345678如果我想四舍五入到从顶部开始的第 3 位数字,那就是 12300000,但如果后面的数字是 1235000 或更大,则四舍五入。5 是 10 的 1/2,二进制 1 中的基数(十进制)是基数的 1/2,所以如果我们要四舍五入的位置之后的数字是 1,那么四舍五入,否则不要。因此,对于 0.7,我们没有四舍五入,对于 0.6,我们四舍五入。

现在很容易看出

00110011001100110011010

由于 (x<0.7) 转换为双精度

00110011001100110011010000000000....

大于

00110011001100110011001100110011....

因此,不必谈论使用等于,问题仍然存在,0.7 是双精度,0.7f 是单精度,如果它们不同,则操作将提升到最高精度。

-2赞 Muhammad Azam 2/10/2019 #8

保存在变量和常量中的指向值的数据类型不同。这是数据类型精度的差异。 如果将 f 变量的数据类型更改为 double,它将打印 equal,这是因为浮点数中的常量默认存储在 double 中,非浮点存储在 long 中,double 的精度高于 float。如果您看到浮点数转换为二进制转换的方法,那就完全清楚了