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.
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://learn.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.