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. Dismiss Notice

Why Doesn't This Code Work?

Discussion in 'Scripting' started by Megalogue1, Jun 4, 2021.

  1. Megalogue1

    Megalogue1

    Joined:
    Dec 17, 2020
    Posts:
    134
    Maybe I'm just sleep-deprived, but I can't see a single reason why this code doesn't work. I came across the issue while using Scriptable Objects in one of my Unity projects, but was able to replicate it in a short C# program on .NET Fiddle.

    The output is
    Does 5 equal 5? False


    Can someone explain this to me?

    Code (CSharp):
    1. using System;
    2.                    
    3. public class Program
    4. {
    5.     public static void Main()
    6.     {
    7.         Test t = new Test();
    8.         float five = 5;
    9.         Console.Write("Does " + five + " equal " + (1 / t.num) + "? " + (five == (1 / t.num)));
    10.     }
    11. }
    12.  
    13. public class Test
    14. {
    15.     private float _num;
    16.    
    17.     public float num
    18.     {
    19.         get
    20.         {
    21.             return _num;
    22.         }
    23.     }
    24.    
    25.     public Test()
    26.     {
    27.         _num = 0.2f;
    28.     }
    29. }
     
  2. DejaMooGames

    DejaMooGames

    Joined:
    Apr 1, 2019
    Posts:
    108
    The issue you are seeing is due to how floating point numbers are handled in c# (and many other languages). You need to compare them with another function that allows for some tolerance that you provide. Look at this article, it describes the issue in detail.
     
  3. Megalogue1

    Megalogue1

    Joined:
    Dec 17, 2020
    Posts:
    134
    I've definitely heard of the difficulties that can arise from floating point precision. Now that I know the issue is float-related (I assumed it was something property-related before), I simplified the code to this, which still gives the same output:

    Code (CSharp):
    1. using System;
    2.                  
    3. public class Program
    4. {
    5.     public static void Main()
    6.     {
    7.         float five = 5;
    8.         float pointTwo = 0.2f;
    9.         Console.Write("Does " + five + " equal " + (1 / pointTwo) + "? " + (five == (1 / pointTwo)));
    10.     }
    11. }
    I did read the article you linked, but one particular aspect of this situation still doesn't make sense to me. If I assign the expression "1 / pointTwo" to a float variable first, I get the "correct" output.

    Code (CSharp):
    1. using System;
    2.                  
    3. public class Program
    4. {
    5.     public static void Main()
    6.     {
    7.         float five = 5;
    8.         float pointTwo = 0.2f;
    9.         float otherFive = 1 / pointTwo;
    10.         Console.Write("Does " + five + " equal " + otherFive + "? " + (five == otherFive));
    11.     }
    12. }
    If the value of "1 / pointTwo" wasn't quite five, but slightly more or less than five by some extremely small margin, why would that change just be assigning that value to a variable? Again, the code now reports that five and otherFive are EXACTLY equal.
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,171
    This will explain it all.

    In an IEEE single-floating point number you can represent certain numbers perfectly and unambiguously.

    For instance, 0.5f is precisely represented as 0x3f000000

    However, 0.1f CANNOT be precisely represented. One possible approximate representation is 0x3DCCCCCD

    The analogy is that you cannot show 1/3rd precisely as a decimal.

    0.33 is correct to 2 decimal places
    0.3333 is correct to 4 places
    etc.

    But you can never exactly represent 1/3rd as a decimal numeral. You just get closer.

    Three times 0.33 (one possible representation of 1/3rd) is 0.99... and last I checked, 0.99 does not equal 1.0... and yet "three times one third" should be 1.0.

    Since computers store everything internally as binary (that's how digital computers work!), you can NEVER, no matter how big a binary number you make, precisely represent 0.1f

    And in your example 0.2f is simply 0.1f times 2, so not representable precisely.

    But you CAN use other ways to represent it, such as with a C# Decimal, but that is nowhere near as efficient computationally, and is rarely ever used. I don't even know if Unity's .NET supports Decimal.
     
  5. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,733
    And a broken clock is right twice a day. The fact that you occasionally get the correct answer doesn't mean that there are any circumstances you can expect floating point imprecision to not affect the result. The advice is the same: Never use == to compare any floats on which any math has been performed. Use Mathf.Approximately, or check against a distance or range.
     
  6. Megalogue1

    Megalogue1

    Joined:
    Dec 17, 2020
    Posts:
    134
    Wow, this whole discussion really makes me a lot more wary of using floats at all...which is a weird feeling, because we kind of need to use floats all the time!

    I found an answer on Stack Overflow saying that it's "very, very hard, if not outright impossible," to write a function that will correctly compare ANY float to ANY other float (https://stackoverflow.com/questions/3874627/floating-point-comparison-functions-for-c-sharp). Any input on this? If true, I even have to be skeptical about Mathf.Approximately, and about any tolerance range I come up with on my own.

    It feels like a "speed bump" in my workflow that I'll constantly have to deal with from now on. A necessary one, probably, and there's no point complaining about something necessary, but still. Can anyone tell me I'm overreacting? Do you ever get held up like this in your projects, just because Mathf.Approximately (or a similar function) randomly had a slightly-too-small tolerance?
     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,171
    Yes, but that's okay. It feels a lot worse than it actually is.

    In practice it is never has to be a limitation. A tiny bit of extra code and you're good.

    In practice, floats are awesome, just not for comparing with equality.

    That's the tl;dr from all this: just NEVER think "is this equal to that?" with floats / doubles

    With integers and strings you can do equality comparisons all day long, just not
    float
    /
    double
    .

    And don't use
    double
    unless you truly understand why you need to, and when you are in Unity, you also understand where double gets off the train and
    float
    takes over inside Unity.

    No because I don't use it. If I need to know exactly when a float is crossing a boundary I keep the previous and compare it to current.

    Code (csharp):
    1. private float prevValue;
    Code (csharp):
    1. void Update()
    2. {
    3.   if ((currValue >= TriggerValue) && (prevValue < TriggerValue))
    4.   {
    5.      BoomTrigger();
    6.   }
    7.   prevValue = currValue; // ninja-edited to stick this in; I forgot it on the first post
    8. }
     
    Last edited: Jun 5, 2021
  8. Megalogue1

    Megalogue1

    Joined:
    Dec 17, 2020
    Posts:
    134
    Great advice. This has been a really valuable lesson--thanks everyone!
     
    Kurt-Dekker likes this.
  9. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,429
  10. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,429
    Note: by "compare" they mean compare for equality not comparing in general. In almost all cases when you want to compare two floats you want to check if one is larger or smaller than the other.
     
    Megalogue1 and Vryken like this.
  11. Megalogue1

    Megalogue1

    Joined:
    Dec 17, 2020
    Posts:
    134
    Hey, I love Computerphile's stuff! I have watched that video and found it very interesting.

    Yeah, come to think of it, I usually only compare floats with > or <, such as when a timer based on Time.deltaTime elapses. It took a very specific situation to reveal the gap in my understanding!