Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

float compute_eps() {
  float eps = 1.0f;

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

  return eps;
}

In the comments it was proposed that the 1.0f + eps != 1.0f test might fail because C++ standard permits the use of extra precision. Although I'm aware that floating-point operations are actually performed in higher precision (than specified by the actual types used), I happen to disagree with this proposal.

I doubt that during the comparison operations, such as == or !=, the operands are not truncated to the precision of their type. In other words, 1.0f + eps can of course be evaluated with the precision higher than float (for example, long double), and the result will be stored in the register that can accommodate long double. However, I think that before performing the != operation left operand will be truncated from long double to float, hence the code can never fail to determine eps precisely (i.e. it can never do more iterations than intended).

I haven't found any clue on this particular case in C++ standard. Furthermore, the code works fine and I'm sure that the extra precision technique is used during its execution because I have no doubt that any modern desktop implementation in fact uses extra precision during calculations.

What do you think about it?

like image 404
Alexander Shukaev Avatar asked Oct 04 '22 06:10

Alexander Shukaev


1 Answers

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
like image 184
Pascal Cuoq Avatar answered Oct 13 '22 11:10

Pascal Cuoq