I created this test app in Visual C++ using Visual Studio 2017 just now:
const double s = 3.0 / 7.0;
if (s == 3.0 / 7.0)
MessageBox(L"match", L"", 0);
else
MessageBox(L"no match", L"", 0);
The messagebox displayed says "match".
Explain.
Printable View
I use exactly the same code to compile LaserBoy in Linux and Windows. My Windows machine is actually a virtual machine running on my Linux machine. So both operating systems are seeing exactly the same code files and running on the same CPU / FPU. My centroid algorithm works perfectly in Linux even on a frame set designed specifically to be hard to deal with. It fails in Windows (without the compiler switches to fix it). This is the first time I have ever seen math that works perfectly in Linux but not in Windows. If an algorithm doesn't work 100% if the time, it's worthless. So I looked into it.
To be more specific, the Windows version of the executable fails to properly identify three point angles that should be zero or pi radians. And I'm not looking for an exact match to zero or pi. I'm looking for all angles that are less than or equal to pi.
That is when I found out about the variations on compliance to the IEEE 754 floating point standards by operating systems (all running on X86 / X87 hardware).
https://en.wikipedia.org/wiki/IEEE_754
If you are really interested, look up how each OS differs in compliance to these rules.
There are countless web pages about the issue.
The specifics of this issue run pretty deep. It is common for a compiler to transform arithmetic operations to make them use fewer CPU cycles to calculate. Logically, they rely on Algebraic concepts like commutative, associative and distributive principles and the fact that subtraction is the opposite of addition, division is the inverse of multiplication and some other stuff like that. The exact order of operation on floating point numbers has everything to do with the precision of the results because of rounding. I don't mean rounding floating point number to integers. I mean rounding real numbers to fit within the representational limits of a fixed number of bits in a floating point binary number (like the square root of two for example). C and C++ runtime libraries differ in the way they do that. They also differ in the way the compiler translates ASCII C or C++ representations of floating point numbers when constructing them in the registers of the CPU / FPU and writing them into the memory.
Like I said, I broke math!
BTW I plan on releasing a new version of LaserBoy soon that includes both the concept of the 3D centroid for rotation and scaling plus my own implementation of 3D vector clipping!
That's some pretty cool stuff! Now I can write frame and frame set effects that move scale or rotate vectors out of the bounds of short integer space and LaserBoy knows how to calculate all of the points where the 3D vectors cross the bounds. 3D clipping can be turned on in the LaserBoy vector art editor as well so you can scale move or rotate objects out of space and LaserBoy knows how to deal with it.
That's simple. It was a bad example. If you are really interested in the issue rather than just telling me I'm wrong, look into it. There are test code sets you can experiment with yourself. DCHammonds tells me there is a setting in Visual Basic that changes the floating point mode of the CPU / FPU. So what is that for?
That’s why is not good to do comparisons for equality using the == operator on floating point numbers.
There are plenty of computer science papers on this subject. There is no one right way to do it. But using an ‘epsilon’ with the degree of resolution you require, with a fabs() is a better way to do it. Depending on the circumstances a ‘relative epsilon comparison’ may be required.
== != a comparison for equality with floating point numbers though.
James
The worst thing about this issue is that you may not know it is the cause of your code problems.
It's not a matter that it breaks floating point math for every case you test. It's a matter that it doesn't!
It is entirely unpredictable.
How else do you explain that a comparison operation of if(a <= pi) ... works exactly as expected in Linux but not in the default behavior of Windows?
In case you are wondering....
const double pi = (4 * atan(1.0));
James.
if(a <= pi) is the wrong way to do safe floating point comparison though...
How would you do it?
I get why an exact equality test is a bad idea, and therefor inequality is also unreliable, but <= is an entire side of the real number line. Adding or subtracting some fudge factor for this kind of comparison doesn't make any sense to me.