This is a result from floating point precision errors. The problem is that printing the values as readable text simplifies the actual representation which skews your expectations. I looked more closely at what happens at the binary level.
I wrote a script to analyse your information.
using System;
using System.Text;
using System.Linq;
using UnityEngine;
public class FloatingPointPrecision : MonoBehaviour
{
void Start()
{
float value = 0.2475f;
int manaBars = 3;
float step = value / manaBars;
float lhs = 0.9075f;
float rhs = 0.0825f;
float incr = IterateTo(lhs, step);
float expectedResult = 0.99f;
printFloat("lhs", lhs);
printFloat("incr", incr);
printFloat("rhs", rhs);
printFloat("step", step);
printFloat("lhs+rhs", lhs + rhs);
printFloat("incr+step", incr + step);
printFloat("Expected result", expectedResult);
}
static float IterateTo(float length, float step)
{
float incr = 0;
for (; incr < length - step; incr += step) ;
return incr;
}
static void printFloat(string label, float value)
{
print(label + "
"
+ ToHexString(value) + " : "
+ ToBinString(value) + " : "
+ value.ToString());
}
static string ToHexString(float value)
{
byte[] raw = BitConverter.GetBytes(value);
StringBuilder sb = new StringBuilder(raw.Length * 2);
foreach (byte b in raw.Reverse())
{
sb.Append(b.ToString("x2"));
}
return sb.ToString();
}
static string ToBinString(float value)
{
byte[] raw = BitConverter.GetBytes(value);
StringBuilder sb = new StringBuilder(raw.Length * 8);
foreach (byte b in raw.Reverse())
{
sb.Append(Convert.ToString(b, 2).PadLeft(8, '0'));
}
return sb.ToString();
}
}
I also include output, formatted slightly differently to make it easy to follow
lhs 3f6851ec : 00111111011010000101000111101100 : 0.9075
incr 3f6851eb : 00111111011010000101000111101011 : 0.9075
rhs 3da8f5c3 : 00111101101010001111010111000011 : 0.0825
step 3da8f5c3 : 00111101101010001111010111000011 : 0.0825
lhs+rhs 3f7d70a4 : 00111111011111010111000010100100 : 0.99
incr+step 3f7d70a3 : 00111111011111010111000010100011 : 0.9899999
Expected result 3f7d70a4 : 00111111011111010111000010100100 : 0.99
You expect lhs (0.9075) + rhs (0.0825) to be 0.99. And it is correct. However, incrementing a variable by a float will cause rounding errors because a float cannot represent every possible fractional value. So, you expect incr to be the same as lhs, and, well, textually it is. But in actuality, there are small differences that can be seen when we look at the binary output.
lhs 3f6851ec : 00111111011010000101000111101100 : 0.9075
incr 3f6851eb : 00111111011010000101000111101011 : 0.9075
Notice that **3f6851ec ** is the hex for 0.9075. **3f6851eb ** is also the hex for 0.9075. But they aren’t the same numbers (…ec, …eb)
The textual error is only visible when we add 0.0825 to lhs and incr.
lhs+rhs 3f7d70a4 : 00111111011111010111000010100100 : 0.99
incr+step 3f7d70a3 : 00111111011111010111000010100011 : 0.9899999
Note that rhs and step is the same value, so don’t get confused that we add different values to lhs and incr…
rhs 3da8f5c3 : 00111101101010001111010111000011 : 0.0825
step 3da8f5c3 : 00111101101010001111010111000011 : 0.0825
This isn’t a “bug” (well, it is in your code, I guess) but a behaviour of floating point binary arithmetic.