float.Epsilon ≈ 0 on PS4?
26 Mar 2017 in blogJust 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.
The Problem
// 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
.
Some Background
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)
)
What Happened?
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. :)