【问题标题】:Can I expect float variable values that I set from literal constants to be unchanged after assignment to other variables?我可以期望我从文字常量设置的浮点变量值在分配给其他变量后保持不变吗?
【发布时间】:2009-09-21 10:42:03
【问题描述】:

如果我这样做:

float a = 1.5f;
float b = a;

void func(float arg)
{
  if (arg == 1.5f) printf("You are teh awresome!");
}

func(b);

文本是否每次都打印(在每台机器上)?

编辑

我的意思是,即使我没有进行任何计算,我也不确定该值是否会在某个时候通过 FPU,如果是,FPU 是否会更改该值的二进制表示。我在某处读到(近似)相同的浮点值在 IEEE 754 中可以有多个二进制表示。

【问题讨论】:

    标签: c++ c comparison floating-point


    【解决方案1】:

    首先 1.5 可以准确地存储在内存中,所以对于这个特定的值,是的,它总是正确的。

    更一般地说,我认为只有在您进行计算时才会出现不准确性,如果您只是存储一个值,即使它没有准确的 IEEE 表示它总是会映射到相同的值(所以0.3 == 0.3,即使0.3 * 3 != 0.9)。

    【讨论】:

    • +1,我也这么认为。不过要当心(因为提问者使用浮点文字而您使用双文字),0.3f != 0.3
    • -1: 变量的存储可能与浮点字面值的 fpu 表示不同。
    • “更一般地说,我认为只有在你进行计算时才会出现不准确性”。在 x86 FPU 上,这 - 奇怪的是 - 并不总是如此。有关示例,请参见 stackoverflow.com/questions/1338045/gcc-precision-bug/…
    • 老实说,如果 operator== for floats 没有定义返回 true 的情况,我不完全相信它应该存在......
    • 并不是'=='永远不会返回true;问题是它不能可靠地返回 true。
    【解决方案2】:

    在示例中,值 1.5F 在 IEEE 754 中有一个精确的表示(以及几乎任何其他可以想象的二进制或十进制浮点表示),所以答案几乎肯定是肯定的。但是,不能保证,并且可能存在编译器无法实现结果。

    如果将值更改为不带exact binary representation 的值,例如5.1F,则结果无法保证。

    Kernighan & Plauger 在他们出色的经典著作《The Elements of Programming Style》中说:

    一位聪明的程序员曾经说过,“浮点数就像沙堆;每移动一个,就会丢掉一点沙子,捡起一点泥土”。经过几次计算后,事情可能会变得很脏。

    (这是我多年前强调的书中的两个短语之一1。)

    他们也观察:

    • 10.0 乘以 0.1 几乎永远不会是 1.0。
    • 不要为了相等而比较浮点数

    这些意见是在 1978 年提出的(第二版),但今天仍然基本有效。

    如果在最受限制的范围内查看问题,您可能没问题。如果问题千差万别,你被咬的可能性比没有被咬的可能性更大,而且你可能迟早会被咬。


    1 另一个突出显示的短语是(减去项目符号):

    • 子例程调用允许我们总结参数列表中的不规则性 [...]
    • [t]子程序本身总结了代码的规则 [...]

    【讨论】:

      【解决方案3】:

      如果它在某个时间点通过 FPU,可能是由于编译器的优化和寄存器处理导致您最终将 FPU 寄存器与内存中的值进行比较。第一个可能比后一个具有更高的精度,因此比较会给您一个错误。 这可能因编译器、编译器选项和您运行的平台而异。

      【讨论】:

        【解决方案4】:

        这是一个(不是很全面的)证明(至少在 GCC 中)你可以保证浮动文字的平等。

        生成文件的Python代码:

        print """
        #include <stdio.h>
        
        int main()
        {
        """
        import random
        chars = "abcdefghijklmnopqrstuvwxyz"
        randoms = [str(random.random()) for _ in xrange(26)]
        for c, r in zip(chars, randoms):
            print "float %s = %sf;" % (c, r)
        
        for c, r in zip(chars, randoms):
            print r'if (%s != %sf) { printf("Error!\n"); }' % (c,r)
        
        print """
            return 0;
        }
        """
        

        生成文件的片段:

        #include <stdio.h>
        
        int main()
        {
        
        float a = 0.199698325654f;
        float b = 0.402517512357f;
        float c = 0.700489844438f;
        float d = 0.699640984356f;
        if (a != 0.199698325654f) { printf("Error!\n"); }
        if (b != 0.402517512357f) { printf("Error!\n"); }
        if (c != 0.700489844438f) { printf("Error!\n"); }
        if (d != 0.699640984356f) { printf("Error!\n"); }
        
            return 0;
        }
        

        并且正确运行它不会在屏幕上打印任何内容:

        $ ./a.out $

        但这里有一个问题:如果你不把文字 f 放在浮点数之后检查是否相等,它每次都会失败!不过,您可以将文字 f 排除在分配之外,没有任何问题。

        【讨论】:

          【解决方案5】:

          我查看了 VS2005 编译器的反汇编。在运行这个简单的程序时,我发现float f=.1;之后,条件f==.1导致...... FALSE。

          编辑 - 这是因为比较对象是 double。当使用 float 文字(即 .1f)时,比较结果为 TRUE。在将 double 变量与 double 文字进行比较时,这也成立。

          我这里添加了源码和反汇编:

            float f=.1f;
          0041363E  fld         dword ptr [__real@3dcccccd (415764h)] 
          00413644  fstp        dword ptr [f] 
          
            double d=.1;
          00413647  fld         qword ptr [__real@3fb999999999999a (415748h)] 
          0041364D  fstp        qword ptr [d] 
          
            bool ffequal = f == .1f;
          00413650  fld         dword ptr [f] 
          00413653  fcomp       qword ptr [__real@3fb99999a0000000 (415758h)] 
          00413659  fnstsw      ax   
          0041365B  test        ah,44h 
          0041365E  jp          main+4Ch (41366Ch) 
          00413660  mov         dword ptr [ebp-0F8h],1 
          0041366A  jmp         main+56h (413676h) 
          0041366C  mov         dword ptr [ebp-0F8h],0 
          00413676  mov         al,byte ptr [ebp-0F8h] 
          0041367C  mov         byte ptr [fequal],al 
          // here, ffequal is true
          
            bool dfequal = d == .1f;
          0041367F  fld         qword ptr [__real@3fb99999a0000000 (415758h)] 
          00413685  fcomp       qword ptr [d] 
          00413688  fnstsw      ax   
          0041368A  test        ah,44h 
          0041368D  jp          main+7Bh (41369Bh) 
          0041368F  mov         dword ptr [ebp-0F8h],1 
          00413699  jmp         main+85h (4136A5h) 
          0041369B  mov         dword ptr [ebp-0F8h],0 
          004136A5  mov         al,byte ptr [ebp-0F8h] 
          004136AB  mov         byte ptr [dequal],al 
          // here, dfequal is false.
          
            bool ddequal = d == .1;
          004136AE  fld         qword ptr [__real@3fb999999999999a (415748h)] 
          004136B4  fcomp       qword ptr [d] 
          004136B7  fnstsw      ax   
          004136B9  test        ah,44h 
          004136BC  jp          main+0AAh (4136CAh) 
          004136BE  mov         dword ptr [ebp-104h],1 
          004136C8  jmp         main+0B4h (4136D4h) 
          004136CA  mov         dword ptr [ebp-104h],0 
          004136D4  mov         al,byte ptr [ebp-104h] 
          004136DA  mov         byte ptr [ddequal],al 
          // ddequal is true
          

          【讨论】:

          • 我想知道,你能把机器代码也贴出来吗?这很奇怪,因为FLDFCOMP 都可以接受相同的内存操作数。我想知道,如果你使用 0.1f 而不是 0.1 你会得到正确的结果。在 C 中,0.1 是一个双精度字面值,而 f 是一个浮点数。这可能解释了比较失败的原因。
          • 无论哪种方式,上面示例中的 fldfcomp 都从内存中获取文字,因为 x87 指令不接受直接参数。
          • 我收回我的“立场更正”评论,除非在将双精度固定为浮动后比较返回 false。
          • @Nathan Fellan:你是对的:对于0.1f,文字值__real@3fb99999a0000000 用于fcomp 函数!
          • 那初始化f的fld是用什么?
          【解决方案6】:

          我不认为它会表现得一样:

          float f1 = .1; 
          // compiled as
          //// 64-bits literal into EAX:EBX
          
          
          if( f1 == .1 ) 
          //// load .1 into 80 bits of FPU register 1
          //// copy EAX:EBX into FPU register 2 (leaving alone the last 16 bits)
          //// call FPU compare instruction
          ////
          

          当编译器生成上述代码时,条件永远不会为真:将 .1 的 64 位版本与 80 位版本进行比较时,16 位将有所不同。

          结论:不:你不能保证每台机器上的平等与此代码。

          【讨论】:

          • 你能显示生成的程序集吗?我想知道为什么它会通过 eax:ebx。什么指令将 eax:ebx 复制到 FPU 寄存器?
          • ...如何将 0.1 直接加载到 FPU 寄存器中?
          • 你的字面价值应该是0.1F 是浮动的,而不是双倍的。
          【解决方案7】:

          它应该每次都在每台机器上打印。您没有使用任何计算的输出,因此没有理由假设这些值会发生变化。

          一个警告:某些“神奇”数字不一定来自记忆。在 x86 中,我们有以下指令:

          • FLDZ - 将 0.0 加载到 ST(0) 中
          • FLD1 - 将 1.0 加载到 ST(0)
          • FLD2T - 将 log2(10) 加载到 ST(0) 中
          • FLD2E - 将 log2(e) 加载到 ST(0) 中
          • FLDPI - 将 pi 加载到 ST(0)
          • FLDLG2 - 将 log10(2) 加载到 ST(0) 中
          • FLDLN2 - 将 ln(2) 加载到 ST(0) 中

          因此,如果您碰巧与其中一个值进行比较,您可能无法获得与您引用的 const 文字完全相同的值。

          【讨论】:

          • -1: bool operator==( float, float ) 一个计算,可能编译为FPU指令。
          • 对,但是operator== 的输入本身并不是任何计算的输出。它们是来自内存的原始数据。因此它们应该始终相等,并且比较也应该返回 true
          • 它们不是来自记忆。第二个参数是浮点字面量。
          • 来自代码,驻留在内存中。例如,在 x86 中,我希望常量在内存中,并使用来自该地址的 fld 指令加载它。
          • 在期待更多之前,看看我最新答案中的代码。
          猜你喜欢
          • 1970-01-01
          • 2021-07-25
          • 2019-08-31
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-11-01
          • 1970-01-01
          相关资源
          最近更新 更多