Floating-point equality test and extra precision: can this code fail?

前端 未结 1 2136
闹比i
闹比i 2021-01-19 09:55

The discussion started under my answer to another question. The following code determines machine epsilon:

float compute_eps() {
  float eps         


        
相关标签:
1条回答
  • 2021-01-19 10:34

    Sorry that this example is C and not C++. It should not be difficult to adapt:

    ~ $ gcc -mfpmath=387 -mno-sse2  c.c
    ~ $ ./a.out 
    incredible but true.
    ~ $ gcc -mfpmath=sse -msse2  c.c
    ~ $ ./a.out 
    ~ $ cat c.c
    #include "stdio.h"
    
    double d = 3. / 7.;
    double d1 = 3.;
    
    int main() {
      if (d != d1 / 7.)
        printf("incredible but true.\n");
      return 0;
    }
    

    gcc -msse2 -mfpmath=sse is a strict IEEE 754 compiler. With that compiler, the if is never taken. However, gcc -mno-sse2 -mfpmath=387 has to use the 387 unit with its higher precision. It does not reduce the precision before the != test. The test ends up comparing the extended-precision result of 3. / 7. on the right-hand side to the double-precision result of the same division on the left-hand side. This cause a behavior that may appear strange.

    Both gcc -msse2 -mfpmath=sse and gcc -mno-sse2 -mfpmath=387 are standard-compliant. It is only the case that the former has it easy, generating SSE2 instructions, and thus can provide a strict IEEE 754 implementation, whereas the latter has to do its best with an ancient instruction set.

    A loop such as:

    while (eps1 != 1.0f)
      eps /= 2.0f, eps1 = 1.0f + eps;
    

    with eps1 declared of type float should be more robust with respect to extended precision.


    The compiler that generates x87 code that does not truncate before comparison is this one:

    ~ $ gcc -v
    Using built-in specs.
    Target: i686-apple-darwin11
    Configured with: /private/var/tmp/llvmgcc42/llvmgcc42-2336.11~148/src/configure --disable-checking --enable-werror --prefix=/Applications/Xcode.app/Contents/Developer/usr/llvm-gcc-4.2 --mandir=/share/man --enable-languages=c,objc,c++,obj-c++ --program-prefix=llvm- --program-transform-name=/^[cg][^.-]*$/s/$/-4.2/ --with-slibdir=/usr/lib --build=i686-apple-darwin11 --enable-llvm=/private/var/tmp/llvmgcc42/llvmgcc42-2336.11~148/dst-llvmCore/Developer/usr/local --program-prefix=i686-apple-darwin11- --host=x86_64-apple-darwin11 --target=i686-apple-darwin11 --with-gxx-include-dir=/usr/include/c++/4.2.1
    Thread model: posix
    gcc version 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)
    

    Here is another:

    ~ $ clang -mno-sse2  c.c
    ~ $ ./a.out 
    incredible but true.
    ~ $ clang -v
    Apple LLVM version 4.2 (clang-425.0.24) (based on LLVM 3.2svn)
    Target: x86_64-apple-darwin12.3.0
    Thread model: posix
    
    0 讨论(0)
提交回复
热议问题