float.Epsilon ≈ 0 on PS4?26 Mar 2017 in blog
Just wanted to write up a quick post about an issue I ran into the other day at work. Not to be dramatic, but mathematics sort of fell apart.
// This is Unity C# code that's targeting PC, PS4, and XB1 float i = 0f; assert(i == 0f); // True everywhere. assert(float.Epsilon != 0f); // True everywhere. assert(i < float.Epsilon); // False on PS4, True elsewhere. This is how I found the problem. assert(float.Epsilon != i); // False on PS4, True elsewhere. Debug.Log( float.Epsilon.ToString('r') ); // 0 on PS4, 1.401298E-45 elsewhere.
So it seems that float.Epsilon on the PS4 is not only evaluating to 0, but also fails the transitive equality property since:
0 == i,
i == float.Epsilon, but
0 != float.Epsilon.
If you’re unfamiliar with floating point numbers you may want to check this video out!
And a quick review of epsilon values, they are meant to be the smallest representable value that isn’t 0 for the given floating type. They are often used in comparisons to mitigate rounding issues. (i.e.
if ( Math.abs(a - b) < float.Epsilon ) instead of
if( a == b))
It boils down to a difference in what C#’s single class assumes floating point precision to be, and what it actually is on the PS4.
Normally when programming for the PS4 you’d be using C++ and it’s std::numeric_limits definition of float epsilon, which would use the macro value set for the target platform.
But using float.Epsilon in C# is just a constant evaluation to 1.401298E-45, so when this code gets run on the PS4 hardware and it doesn’t have precision to represent that number, it rounds it to 0 and things fall apart (in a weird mathematical rule breaking sort of way).
The Fix (or at least the workaround)
Use Unity’s Mathf.Epsilon. Under the hood this one actually does some determination to find the correct value for the given platform. So your code is once again portable and your comparisons to epsilon will once again make sense. :)