Why does this formula break after 31 loops?

I have the following code:

public float SnapValueToCoreBlock(float ValueToSnap)
{
    ValueToSnap = ValueToSnap + 0.5f;
    ValueToSnap = Mathf.Floor(ValueToSnap);
    return ValueToSnap;
}

float floatvar;
int intvar;


    for (int z = 0; z < 100; z++)
    {

        floatvar = z + (Mathf.FloorToInt(0.499999f) + 1) * 0.499999f / (Mathf.FloorToInt(0.499999f) + 1);
        intvar = (int)SnapValueToCoreBlock(floatvar);
    }

I am expecting “intvar” to always be equal to “z” in the loop, however after 31 iterations there is some kind of rounding error and when z=32 intvar = 33, instead of z=32 intvar =32

from then onwards intvar is always off by 1, so for z<32 intvar=z and for z >31 intvar=z+1

my intended result is for z = intvar always, I dont understand why this arbitrarily changes when z reaches 32, I would appreciate it if someone could help me, Thanks in advance.

Answer

I took your code and translated them from Unity to the .NET Framework. This involved changing Mathf.FloorToInt(someFloat) to (int)Math.Floor(someFloat) and Mathf.Floor(ValueToSnap) to (float)Math.Floor(ValueToSnap). I believe it does the same thing, but it takes a detour through double.

I also inserted a WriteLine statement in your loop:

for (int z = 0; z < 100; z++)
{
    floatvar = z + ((int)Math.Floor(0.499999f) + 1) * 0.499999f / ((int)Math.Floor(0.499999f) + 1);
    intvar = (int)SnapValueToCoreBlock(floatvar);
    Debug.WriteLine($"FloatVar: {floatvar} Z: {z} IntVar: {intvar}");
}

I see the same behaviour.

In particular, I see breaks at z == 9:

FloatVar: 8.499999 Z: 8 IntVar: 8
FloatVar: 9.499999 Z: 9 IntVar: 9
FloatVar: 10.5 Z: 10 IntVar: 10
FloatVar: 11.5 Z: 11 IntVar: 11

and at z == 32:

FloatVar: 30.5 Z: 30 IntVar: 30
FloatVar: 31.5 Z: 31 IntVar: 31
FloatVar: 32.5 Z: 32 IntVar: 33
FloatVar: 33.5 Z: 33 IntVar: 34

Even if I extend the precision of the output beyond the precision of the float:

Debug.WriteLine($"FloatVar: {floatvar:0.0000000000000000000} Z: {z} IntVar: {intvar}");

I see the same behaviour, rounding down below 32 and rounding up above it.

Then I greatly simplified your calculation:

floatvar2 = z + 0.499999f;
intvar2 = (int)SnapValueToCoreBlock(floatvar2);

And I still see the same behaviour.

So, what it seems is that:

 anInteger + 0.499999f + 0.5f;

is less than anInteger + 1.0f for values of anInteger < 32 and equal to or greater than anInteger + 1.0f for values anInteger >= 32. And, you know what, that doesn’t surprise me. You are right at the edge of floating point precision (remember, floats have ~6-9 digits of precision: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/floating-point-numeric-types). When you went from 31 to 32, you ended up setting another bit, and that’s what likely made the difference.

Final Comment (as an update)

You say “the 0.4999999 is a variable that I am using for collisions”. You need to read up on how to compare floating point values, and how to properly use an epsilon. Here’s one thing I found (https://bitbashing.io/comparing-floats.html). It’s C++ focused, but seems to address the issues. As a final comment, if you are ever using measured values (like in a chemical process control system), you need to include the precision of your measurements in the establishment of an epsilon.

Leave a Reply

Your email address will not be published. Required fields are marked *