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

Question Comparing a clamped float value with an integer (max value)...

Discussion in 'Scripting' started by imiotis, Sep 20, 2023.

  1. imiotis

    imiotis

    Joined:
    Dec 22, 2018
    Posts:
    32
    Good day everyone!

    I have code:

    float a,b;
    float lerp = a/b;
    lerp = Mathf.Clamp(lerp, 0, 1);


    Which of these comparisons will work correctly?
    a) if (lerp == 1)
    OR
    b) if (Mathf.Approximately(lerp, 1))
    ?

    At the moment I'm using a) and it works OK, but I learned that floating numbers need to be compared using Mathf.Approximately.
    In my case I'm comparing a float to an integer and I don't know how that works for c#. Maybe the integer is converted to float when compared?
     
    Last edited: Sep 20, 2023
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    I don't recommend ever comparing float for equality, even if you feed constants in.

    Mathf.Approximately is great, but
    if (lerp >= 1)
    is better. :)

    Or do your own notion of how close it has to be:

    Code (csharp):
    1. if (Mathf.Abs( lerp - 1) < 0.1f) ...
    But remember if your terms move fast enough those will fail too.

    That's why I always like
    >=


    ALSO, just as a note, *.Lerp() will actually clamp the third term already for you.

    ALSO... here's a pattern I really like:

    Code (csharp):
    1. float duration = 2.3f;
    2.  
    3. for (float t = 0; t < duration; t += Time.deltaTime)
    4. {
    5.   float fraction = t / duration;
    6.  
    7. /// do my lerp here using fraction as the third argument
    8.  
    9.    yield return null;
    10. }
    BUT! The above might never actually fire on 1.0 so you have to adjust after the loop exits.

    This is a modified form that WILL perform the final 1.0f lerp, but it can be baffling:

    Code (csharp):
    1. float duration = 2.3f;
    2.  
    3. for (float t = 0; true; t += Time.deltaTime)
    4. {
    5.   float fraction = t / duration;
    6.  
    7. /// do my lerp here using fraction as the third argument
    8.  
    9.    yield return null;
    10.  
    11.    if (fraction >= 1) break;
    12. }
    Of course, if you're just lerping stuff around, perhaps you should be using DOTween or LeanTween or some other tweener. No sense reinventing the wheel!
     
    Last edited: Sep 20, 2023
    imiotis likes this.
  3. imiotis

    imiotis

    Joined:
    Dec 22, 2018
    Posts:
    32
    Thanks!

    I have this code in my project, and it always stops at the right time. And I'm curious why I never got the error. Am I lucky, or is the probability too low?
    Code (CSharp):
    1. float duration = 1.4f;
    2. while (true)
    3. {
    4.     float lerp = time/duration;
    5.     lerp = Mathf.Clamp(lerp, 0, 1);
    6.     if (lerp == 1) break; // always works;
    7.     time += Time.deltaTime;
    8.     yield return null;
    9. }
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    In theory line 5 should clamp it at the constant 1

    This allows your line 6 to succeed, as it compares to a constant.

    However, should you in the future change that value like to 1.1 ... it might stop working.

    Now you're very unlikely to... but stuff like that happens all the time.

    Why do you think software (in general) in this world is so @$$##@$@#$ ridiculously buggy? :)

    Here's some more reading, including a cool visualizer at the bottom.

    Floating (float) point imprecision:

    Never test floating point (float) quantities for equality / inequality. Here's why:

    https://starmanta.gitbooks.io/unitytipsredux/content/floating-point.html

    https://forum.unity.com/threads/debug-log-2000-0f-000-1f-is-200.1153397/#post-7399994

    https://forum.unity.com/threads/why-doesnt-this-code-work.1120498/#post-7208431

    "Think of [floating point] as JPEG of numbers." - orionsyndrome on the Unity3D Forums

    Literal float / double issues:

    https://forum.unity.com/threads/err...annot-be-implicitly-con.1093000/#post-7038139

    And thanks to halley for this handy little visual floating point converter:

    https://www.h-schmidt.net/FloatConverter/IEEE754.html
     
    imiotis likes this.
  5. richard_harrington

    richard_harrington

    Unity Technologies

    Joined:
    Sep 29, 2020
    Posts:
    22
    Just to provide a little deeper explanation on this...

    "Floating point errors" are a side-effect of the fact that fractional numbers (like floats, halves, doubles, etc.) are not "infinite" in precision - they "round" to the "most precise" decimal point they have available. A short basic explanation of this is "the bigger the number on the left side of the decimal point, the less precision you have on the right side of the decimal point".

    This especially comes up when you compare two fractional numbers that are the result of a multiply or, especially, a divide. If you do the same math for two numbers, they will probably end up equal, but different processors will actually round differently, or have different levels of precision - so you may end up with different numbers even with the same math between them.

    Now, specifically to answer your question:
    If you are comparing a float to an int, it should be relatively safe to compare your int to Mathf.CeilToInt(myFloat) or Mathf.FloorToInt(myInt) - though you may still run into trouble when your float could be 0.9999999 instead of 1.0000000 (approximately) on different devices.
    Similarly, Mathf.RoundToInt is relatively safe, but would have issues when the float might be 0.4999999 versus 0.5000000.

    So as Kurt-Dekker pointed out - it's never 100% safe to directly compare floats to anything for equality, but you should be pretty safe comparing for "approximate equality" - which means using Mathf.Approximately is the way to go if you are worried about having consistent results across devices (there may still be issues even there, though, so be careful).
     
    imiotis likes this.
  6. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    280
    Also, you should understand that you are not actually comparing a float to an int. The comparison operators only work on two values of the same type. So, even if you write an int literal, or one of the variables is a float, the comparison is always taking place between a float and a float, or an int and an int. I'm not checking your code specifically in my IDE right now, but if you hover over the comparison operator in Visual Studio with your mouse it will tell you which types are being compared. The types you present are implicitly converted to those types before the comparison. So, you're not really comparing a float to an int. Make sure you understand that and inspect your comparison operator to know which values are being converted to which types for comparison.
     
    richard_harrington likes this.
  7. richard_harrington

    richard_harrington

    Unity Technologies

    Joined:
    Sep 29, 2020
    Posts:
    22
    Good point, and yes, usually if you are doing any operations between integers and decimal numbers, it'll prefer the greatest precision from among them - so int will be converted to float when comparing against a float.

    There are also some weird cases where things get converted to doubles, when you're not actually using any doubles, but that's a whole other can of worms..