Why is Mathf.RoundToInt unreliable?

I’m trying to understand why my formula is off by 1, any mathwiz who can help me?

Debug.Log(Mathf.RoundToInt(1.4f)); // = 1
Debug.Log(Mathf.RoundToInt(1.5f)); // = 2
Debug.Log(Mathf.RoundToInt(1.6f)); // = 2

// my formula, it should return 2 as above but returns 1
Debug.Log(Mathf.RoundToInt(10 * (1f - 0.85f))); // = 1

As per source Unity - Scripting API: Mathf.RoundToInt

0.15 and 0.85 cannot be represented exactly in binary. They are infinite repeating decimal expansions:

You won’t actually get 1.5 from your math. It is probably slightly below that. And then it gets rounded to 1.

It may also depend on the hardware or what have you. Here’s what I get in my javascript console for example:
8132573--1055036--upload_2022-5-16_15-13-54.png

3 Likes

Huh, TIL. What can I do to work around this problem?

In a vacuum? Nothing really. You’d have to explain how this is actually manifesting in your game and why it matters.

2 Likes

What PraetorBlue said.

For 95% of cases the inaccuracy of a float is not important. 4.9% of the time there’s another way around it. And in that 0.1% of the time you just need perfect accuracy… you should use a different data type (for example decimal type for monetary calculations in the business world where the expectations of a base-10 system are not just expected but required by most law).

In a video game being off by 1 pixel is seldom an issue.

So… our question would be, why do this matter? What are you attempting to accomplish, and how is this impacting it? It’s always good to include code.

1 Like

Right, that’s the perfect analysis of the issue ^^. We have way too many questions here that try to reduce / isolate the problem which is a good idea when you are debugging and looking for the “cause” of an issue. However asking a question about general and well known behaviour usually don’t get you very far. That’s why it’s better to focus on the actual goal when asking a question. That way we can actually address the specific usecase and either suggest alternatives or workarounds.

I’m redesigning my health system so the offset is a difference between taking 1 damage or 2 damage (2 being correct value). My calculations are percent-based.

Solved: I changed my formula to Mathf.RoundToInt(amount * (1f - resistancePercent + 0.01f)); and now my tests pass.

A solution would also be the C# decimal type. However, not suitable for more complex calculations, as it is very slow.

You have to be very careful with “solutions” like that unless you have fully analyzed the possible ranges and minimal step of all variables. As much as it helped you with one specific input, it can equally easy cause the result to be rounded wrong way with different numbers. Assuming resistance is in range 0-1 with 0.5 representing 50% , adding 0.01 could cause wrong rounding for something like 49% or 51%. Assuming amount can be larger than 100, that extra 0.01 will give you wrong result even before any rounding. But I can’t know what the actual constraints for values in your code are.

Have you thought about working with integers? Add 2 zeroes to all your numbers (equivalent as working with 2 decimals of precision) and make all your calculations using only integer math.

That’s what I usually do for my health systems and it works well.

3 Likes