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?
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With