Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice
  4. Dismiss Notice

Really weird Math.Min result

Discussion in 'General Discussion' started by Skulltager, Jun 15, 2022.

  1. Skulltager

    Skulltager

    Joined:
    Aug 12, 2016
    Posts:
    25
    I ran into this weird issue where the result of a value differs when changing it from 1 line of code to 2 lines of code even though the execution order is practically the same. I thought both would end up with the same value, but they don't? This doesn't happen if I replace the Mathf.Min(3f, 3f) with just 3f.

    Does anyone know why this is happening?
    upload_2022-6-15_22-21-34.png

    Here's the code if you want to test it out
    Code (CSharp):
    1.     private void Test()
    2.     {
    3.         // Mathf.Min returns the lowest of the values
    4.         float floatResult1 = Mathf.Min(3f, 3f) / 0.3f;
    5.         int intResult1 = (int) floatResult1;
    6.  
    7.         int intResult2 = (int) (Mathf.Min(3f, 3f) / 0.3f);
    8.  
    9.         Debug.Log("float result 1: " + floatResult1);
    10.         Debug.Log("int result 1: " + intResult1);
    11.  
    12.         Debug.Log("int result 2: " + intResult2);
    13.     }
     

    Attached Files:

    Last edited: Jun 15, 2022
  2. Skulltager

    Skulltager

    Joined:
    Aug 12, 2016
    Posts:
    25
    I tried making my own Min function and it results the same problem.

    upload_2022-6-15_22-40-2.png
     
  3. Skulltager

    Skulltager

    Joined:
    Aug 12, 2016
    Posts:
    25
    Tried it with doubles and now I see the rounding error.
    My guess is the compiler works with just doubles and since I'm specifically storing the float, it actually does a float conversion in the first result which it tries to optimize in the second result causing the rounding error.

    upload_2022-6-15_22-49-48.png
     

    Attached Files:

  4. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,137
    I recommend either reposting this in Scripting or asking the moderators to move the thread. This is a question that the people in that section would be better qualified to answer.
     
  5. Skulltager

    Skulltager

    Joined:
    Aug 12, 2016
    Posts:
    25
    Ahh nah it's fine. I think I was just using the forums as a rubber ducky to figure out what was causing this. But now that I know why it occurs the post can be closed all together. xD
     
  6. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,472
    By the way, this has absolutely nothing to do with the min method!
    Just using 3f directly has the same result:
    https://dotnetfiddle.net/0ifnvD

    Indeed the culprit is rounding in a way. The actual computation components in the CPU (ALUs) tend to work with higher precision than float (because they are designed to support doubles at very least) and just throw away the precision when actually storing in a float. Usually this gives you some extra precision at no computation cost, but in edge cases like those, it can suddenly make such a huge difference.
    Note that this is not hardware independent! It's a common cause why the same program can behave differently on other CPU architectures.

    Even worse, there are also other kind of inaccuracies which are the reason why most gaming physics engines (including Unity's) are not deterministic (that means for example throwing around 100 physics balls and letting them bounce against each other, will every time result in different chaos after a minute, even if you started them all with the same positions and velocities):
    https://stackoverflow.com/questions/328622/how-deterministic-is-floating-point-inaccuracy
     
    Last edited: Jun 15, 2022
    CodeSmile likes this.
  7. Skulltager

    Skulltager

    Joined:
    Aug 12, 2016
    Posts:
    25
    using 3f directly doesn't cause this problem for me in unity c# which is why it weirded me out so much in the first place. upload_2022-6-15_23-51-13.png

    But it does actually cause the problem in fiddle
    upload_2022-6-15_23-52-45.png
     
  8. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,472
    Interesting, this is compiler dependent too.
    If you select Roslyn or Net 6 in the fiddle, the issue disappears as well.

    Conclusion:
    Don't make things dependent on values which are even with a 100 digits accurate math, as small as 0.0000....1 xP
     
  9. Skulltager

    Skulltager

    Joined:
    Aug 12, 2016
    Posts:
    25
    Haha yeah I can't believe a different .net compiler will change the result here.
    I actually ran into this problem because I'm used to always use int conversion when I want to round down to int.
    I never used FloorToInt because I thought it was the exact same thing, but FloorToInt will actually return the correct value as well. Guess I'll start using FloorToInt from now on.
     
  10. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,472
    What do you assume the correct value here to be? 9 or 10?
    Mathematically correct it would be 10, but honestly it is very risky to use a statement that only makes sense on a computer if it manages to assume "absolute" precision.

    As you can see here, unity's FloorToInt method consists of just a call to Math.floor and a cast to Int:
    https://github.com/Unity-Technologies/UnityCsReference/search?q="int+FloorToInt"
    I just tested in the fiddle that Math.floor has the same compiler-dependent behavior!

    The correct approach here unfortunately is not very pretty and actually a real challenge!
    Here a small article I found where they wave away the issue more or less by just subtracting a fixed epsilon:
    https://mortoray.com/2016/01/05/the-trouble-with-floor-and-ceil/
    However a constant epsilon can fail quickly: https://stackoverflow.com/questions...or-of-math-floordouble-and-math-ceilingdouble
    It's a challenge that has to be tackled in industries like automobile or aviation where such an edge case could costs lives. There for example I'm not allowed to use even the standard library of C++ because it is prone to this kind of errors. Instead we have special libraries that force us to define the expected value-range of our doubles, so that it can automatically select a fitting value for epsilon internally when computing. However I digress.. xP

    In games you are likely better off to avoid such edge cases.
     
    Last edited: Jun 15, 2022
  11. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,325
    The compiler likely noticed that is a constant, and precalculated the value of the expression for you. Meaning it doesn't actually divide 3 by 0.3 there.

    Anyway, use Mathf.RoundToInt....
     
    DragonCoder likes this.
  12. Murgilod

    Murgilod

    Joined:
    Nov 12, 2013
    Posts:
    9,759
    If you want reproducibility with floating point values you better be prepared to work for them. There's a reason I'm evaluating streflop for my engine for my determinism needs.
     
  13. Skulltager

    Skulltager

    Joined:
    Aug 12, 2016
    Posts:
    25
    I doubt that's the issue since that would mean that the result of the precalculation is different from the runtime result given the same exact inputs. I do think it's compiler optimization related, but just not precalculation based.
    The reason I think using any Mathf rounding function also fixes it is because then, just like first result in my screenshot, the compiler cannot directly use the internal double and convert it to an int. instead it will have to convert the double to a float and then to an int. Looking at how the latest .net version doesn't have this problem at all means it's just an issue I'll have to live with in unity. And using Mathf rounding functions is a good safety net to prevent this issue from appearing again.
     
  14. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,325
    It absolutely can happen, though.

    Because there's no reason to think that the part performing constant value calculation is using the same math as runtime floating point division.
     
    atomicjoe likes this.