【问题标题】:strange output in comparison of float with float literal与 float 和 float 文字相比的奇怪输出
【发布时间】:2010-12-22 19:17:40
【问题描述】:
float f = 0.7;
if( f == 0.7 )
    printf("equal");
else
    printf("not equal");

为什么输出是not equal

为什么会这样?

【问题讨论】:

标签: c++ c floating-point double-precision


【解决方案1】:

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

  if(f == 0.7)

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

  if(f == 0.7f)

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

【讨论】:

  • 是的。 f 后缀(如 0.7f)使它们成为浮点字面量。
  • 或许更重要的是,不要测试浮点值是否完全相等。
  • 是的,Michael 在这里提出了最佳答案。您永远不应该寻找浮点数之间的相等性。这个答案只会在以后导致更多的混乱。
  • “不测试浮点值的相等性”仅基于迷信。整数溢出是有原因的。作为一名程序员,不要放弃理性的道路并宣称“尝试用整数进行计算是没有用的,它们会溢出”。学习整数如何工作以及如何正确使用它们。浮点数也是如此。浮点计算可能准确或不准确是有原因的。原因不难理解。在某些情况下测试相等性是有意义的。
【解决方案2】:

这个答案是对现有答案的补充:请注意,0.7 不能完全表示为浮点数(或双精度数)。如果准确表示,那么在转成float再转回double的时候就不会丢失信息,就不会有这个问题了。

甚至可以争辩说,对于不能精确表示的字面浮点常量应该有一个编译器警告,特别是当标准对于是否在运行时以具有被设置为那个时间或在编译时以另一个舍入模式。

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

【讨论】:

  • “小整数”需要在这里定义——一个 IEEE 浮点数将保存 24 位而不会丢失,或者最多 16777215。一个双精度数保存 54 位,比标准整数大得多。
  • @MarkRansom:对于数学家来说,所有小于 any 固定界限的整数都是“小”的。此外,53 位。
  • @StephenCanon,感谢您的更正,我知道是53 bits,所以我请求暂时精神错乱。
  • "所有可以精确表示的非整数数字都以 5 作为最后一个小数。" .每天我都会学到一些新东西。这就是今天的事情。谢谢!
  • @Floris - 任何可以转换为以 2 为分母的分数的小数都可以精确表示。所有这些分数都将以 5 结尾,因为小数中的分母将是 10 的幂,而您必须将 10 除以 5 才能得到 2,因此分子必须至少能被 5 整除一次。跨度>
【解决方案3】:

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


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


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


**

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

**
我取数字 0.1
它很容易,因为没有整数部分。第一个符号位 - 它是 0。 我现在将计算指数和有效精度。逻辑乘以 2 个整数 (0.1*2=0.2),如果大于 1,则减去并继续。

数字是 .00011001100110011001100110011,标准说我们必须在得到 1 之前向左移动。(某事)。你怎么看我们需要 4 个班次,从这个数字计算 Exponent (127-4=123)。现在有效位精度为
10011001100110011001100(并且有丢失的位)。
现在是整数。符号位0指数为123(01111011),有效位精度为10011001100110011001100,整体为
00111101110011001100110011001100 让我们将其与上一章中的内容进行比较
00111101110011001100110011001101
如您所见,最后一位不相等。这是因为我截断了数字。 CPU 和编译器知道这是在 Significand 精度之后无法保持的东西,只需将最后一位设置为 1。

【讨论】:

  • 即使尾数符合这种格式,0.5 比较如何不起作用?
【解决方案4】:

另一个近乎精确的问题与这个问题有关,因此迟到了几年的答案。我认为以上答案并不完整。

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 中的单精度转换为双精度并与双精度 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 双3fe6666666666666

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

见eeeeeeffffffffffffffffffffffff

双是

见啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊

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 也失败了。


编辑

这里的等号可以从问题中去掉,这是一个已经被回答的不同的问题,但它是同一个问题,也有“what the ...”的初始震惊。

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,如果我想从顶部四舍五入到第三位,那将是 12300000,但如果后面的数字是 5 或更大,则四舍五入到下一个数字 1235000,然后四舍五入。 5 是 10 的 1/2,二进制 1 中的底数(十进制)是底数的 1/2,所以如果我们要四舍五入的位置之后的数字是 1,则四舍五入,否则不要。所以对于 0.7 我们没有四舍五入,对于 0.6 我们确实四舍五入。

现在很容易看到

00110011001100110011010

由于 (x

00110011001100110011010000000000....

大于

00110011001100110011001100110011....

所以不用说使用 equals,问题仍然存在 0.7 是双精度 0.7f 是单精度,如果它们不同,则运算会提升到最高精度。

【讨论】:

    【解决方案5】:

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

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

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

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

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

    【讨论】:

      【解决方案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;
      
              }
      

      【讨论】:

        【解决方案7】:

        考虑一下:

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

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

        示例:

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

        输出:
        0.7000000000 0.6999999881

        【讨论】:

          【解决方案8】:

          保存在变量和常量中的指针值具有不同的数据类型。这是数据类型精度的差异。 如果将f变量的数据类型改为double,它会打印equal,这是因为中存储的浮点常量>double 并且在 long 中默认为非浮点型,double 的精度高于浮点型。看浮点数转二进制的方法就一目了然了conversion

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-07-10
            相关资源
            最近更新 更多