Reputation: 803
I'm currently trying to learn about floating point representation in depth, so I played around a bit. While doing so, I stumbled on some strange behaviour; I can't really work out what's happening, and I'd be very grateful for some insight. Apologies if this has been answered, I found it quite hard to google!
#include <iostream>
#include <cmath>
using namespace std;
int main(){
float minVal = pow(2,-149); // set to smallest float possible
float nextCheck = static_cast<float>(minVal/2.0f); // divide by two
bool isZero = (static_cast<float>(minVal/2.0f) == 0.0f); // this evaluates to false
bool isZero2 = (nextCheck == 0.0f); // this evaluates to true
cout << nextCheck << " " << isZero << " " << isZero2 << endl;
// this outputs 0 0 1
return 0;
}
Essentially what's happening is:
What's going on -- I would have thought them to be identical? Is the compiler trying to be clever, saying that dividing any number cannot possibly yield zero?
Thanks for your help!
Upvotes: 1
Views: 118
Reputation: 80284
The reason isZero
and isZero2
can evaluate to different values, and isZero
can be false, is that the C++ compiler is allowed to implement intermediate floating-point operations with more precision than the type of the expression would indicate, but the extra precision has to be dropped on assignment.
Typically, when generating code for the 387 historical FPU, the generated instructions work on either the 80-bit extended-precision type, or, if the FPU is set to a 53-bit significand (e.g. on Windows), a strange floating-point type with 53-bit significands and 15-bit exponents.
Either way, minVal/2.0f
is evaluated exactly because the exponent range allows to represent it, but assigning it to nextCheck
rounds it to zero.
If you are using GCC, there is the additional problem that -fexcess-precision=standard
has not yet been implemented for the C++ front-end, meaning that the code generated by g++ does not implement exactly what the standard recommends.
Upvotes: 5