关于c ++:与float文件相比较的奇怪输出

strange output in comparison of float with float literal

1
2
3
4
5
float f = 0.7;
if( f == 0.7 )
    printf("equal");
else
    printf("not equal");

为什么输出是not equal

为什么会这样?


这是因为在你的陈述中

1
  if(f == 0.7)

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

1
  if(f == 0.7f)

但是正如Michael在下面的注释中建议的那样,您永远不应该测试浮点值是否完全相等。


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

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

所有可以精确表示的非整数都将5作为最后一个十进制数字。不幸的是,相反的情况并非如此:有些数字的最后一个十进制数字是5,不能精确表示。小整数都可以精确表示,用2的幂除可以将一个可以表示为另一个可以表示的数转换为另一个可以表示的数,只要不进入非规范化数的领域。


首先让我们看一下浮点数。我取0.1f,它是4字节长(二进制32),用十六进制表示3D CC CC光盘。根据标准IEEE 754,要将其转换为十进制,我们必须这样做:

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

enter image description hereenter image description here

我们需要加上所有的数字(2的幂),再加上1(总是1,按标准)。它是1600万2384185791015625现在让我们把这个数字乘以2^(-4),它是指数。我们只是把上面的数字除以2,4次:01000000011490116119384765625我用的是MS计算器**

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

**我取0.1它很容易,因为没有整数部分。第一个符号位-它是0。指数和有效位精度,我现在计算。逻辑乘以2整数(0.1*2=0.2),如果大于1,则继续。enter image description here号码是.000110011001100110011001100110011,斯坦达特说我们必须在得到1之前向左移动。你怎么看,我们需要4个移位,从这个数字计算指数(127-4=123)。有效位精度现在是100110011001100110011001100(并且有丢失的位)。现在是整数。符号位0指数为123(0111011),有效位精度为10011001100110011001100,整数为0011110110011001100110011001100我们把它和上一章的比较一下邮编:001111011001100110011001101如你所见,最后一点是不平等的。因为我截断了这个数字。CPU和编译器知道,在有效位精度之后是不能保持的,只需将最后一位设置为1。


另一个近乎精确的问题与这一问题联系在一起,因此答案迟了几年。我认为上述答案不完整。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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,否则它是一个单精度数。所以

1
  float x=0.7;

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

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

该语言表示,我们必须提高精度,这样x中的单精度转换为双精度,并与双精度0.7进行比较。

1
2
3
4
5
6
7
8
9
10
11
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

单3F33 33双3FE6666666666

正如亚历山德拉指出的那样,如果这个答案仍然是IEEE754,那么一个是

seeeeeeeefffffffffffffffffffffff

双是

seeeeeeeeeeeffffffffffffffffffffffffffffffffffffffffffffffffffff

分数是52位而不是23位。

1
2
3
4
5
00111111001100110011... single
001111111110011001100110... double

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

就像10的1/3是0.3333333…永远。我们这里有一个重复模式0110

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

这就是答案。

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

X包含01100110011001100110011作为其分数,当转换回分数加倍是

1
01100110011001100110011000000000....

不等于

1
01100110011001100110011001100110...

但是这里

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

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

1.0为什么有效?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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...

在这两种情况下,分数都是零。因此,从双精度转换为单精度转换为双精度没有损失。它精确地从单值转换为双值,两个值的位比较工作。

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

答案中没有说明原因。0.7未通过1.0工程。为什么没有显示0.7失败。一个重复的问题1.1也失败了。

编辑

等号可以从这个问题中去掉,这是一个已经被回答过的不同的问题,但它是相同的问题,并且有"什么……"的初始冲击。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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的故事。

1
2
3
4
01100110011001100110011 single, 23 bits
01100110011001100110011001100110.... double 52 bits.

01100110011001100110011000000000....

小于。

1
01100110011001100110011001100110...

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

但是,当从双精度转换为单精度时,或者通常在表示时作为一个单独的IEEE 754。

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

IEEE754使用取整模式,向上取整、向下取整或向零取整。默认情况下,编译器倾向于进行汇总。如果你记得在12345678小学四舍五入,如果我想从顶部四舍五入到第三个数字,它将是12300000,但如果后面的数字是5或更大,则四舍五入到下一个数字1235000。5是10的1/2,二进制1中的基数(十进制)是基数的1/2,所以如果我们要四舍五入的位置后的数字是1,那么四舍五入,否则不。所以对于0.7,我们不求和,对于0.6,我们求和。

现在很容易看出

1
00110011001100110011010

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

1
00110011001100110011010000000000....

大于

1
00110011001100110011001100110011....

所以不用说用等号的问题仍然存在,0.7是双0.7f是单的,如果操作不一致,就提升到最高精度。


网上的很多答案都犯了错误,就是看浮点数之间的异常差,这只对特殊情况有效,稳健的方法是看下面的相对差:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
      // 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;

        }

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

更好的做法是

1
2
3
4
5
float f = 0.7;
if( fabs(f - 0.7) < FLT_EPSILON )
    printf("equal");
else
    printf("not equal");

假设flt_epsilon被定义为平台的适当小的浮点值。

由于舍入或初始化错误不太可能超过flt_epsilon的值,这将为您提供所需的可靠等效性测试。


如果将f的数据类型改为double,则打印为equal,这是因为双精度浮点和长时间非浮点存储的常量,双精度高,浮点精度低,双精度值存储在64位二进制中,浮点值存储在32位二进制中,如果您看到浮点数的方法,就会完全清楚。转换为二进制转换。


考虑一下:

1
2
3
4
5
6
7
8
9
10
11
int main()
{
    float a = 0.7;
    if(0.7 > a)
        printf("Hi
"
);
    else
        printf("Hello
"
);
    return 0;
}

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

例子:

1
2
3
4
5
6
7
int main()
{
    float a=0.7;
    printf("%.10f %.10f
"
,0.7, a);
    return 0;
}

输出:0.70亿0.6999999881