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

Accurate float incrementaion is off by 0.01n

Discussion in 'Scripting' started by mudflaps, Sep 23, 2020.

  1. mudflaps

    mudflaps

    Joined:
    Mar 16, 2017
    Posts:
    8
    Hi,

    Hopefully a basic question, I am trying to increment a float from 0.01f to 5.0f, however when I am incrementing it in a loop on a thread it increments not like this:

    0.01
    0.02
    0.03

    etc

    And instead like:

    i = 0.01
    i = 0.02
    etc.. until
    i = 0.05
    i = 0.05999999
    i = 0.06999999
    and so on, until it gets to bigger numbers where it looks like:
    i = 2172.048
    i = 2172.058
    i = 2172.068

    Perhaps it is my misunderstanding of unity, but why is it doing this? At larger numbers this could cause inconsistent math, what is the best way to increment it to cope with big numbers?

    I am simply doing

    i += 0.01f;


    Thanks!
     
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,724
    It is not Unity's fault. This is how floating point numbers work, and why you should only expect "good enough" rather than perfect calculations using them. Whenever you do math with floating point numbers, you must expect some degree of error in the calculation due to limits in precision.

    https://floating-point-gui.de/errors/propagation/
     
    Vryken and mudflaps like this.
  3. mudflaps

    mudflaps

    Joined:
    Mar 16, 2017
    Posts:
    8
    Thank you! That is really helpful, and I have learned something new and useful today. I'll look into that!
     
  4. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,724
    One way to mitigate this problem in your particular case could be to instead do your incrementation with a fixed-point value. For example, increment an integer from 0 to 500, and calculate the floating point value as needed from that:

    Code (CSharp):
    1. for (int i = 0; i <= 500; i++) {
    2.   float myFloatValue = (float) i / 100f;
    3. }
     
    mudflaps likes this.
  5. mudflaps

    mudflaps

    Joined:
    Mar 16, 2017
    Posts:
    8
    Thank you, I'm actually looking to obtain the number of seconds (x.jn format) a graph runs for which doesnt have a method to get the number of seconds, so I wont know the end value to divide by if that makes sense. I may just use Math to round it up
     
  6. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,724
    I don't understand this, sorry :(

    What do you mean by a graph (there are many meanings)? What does it mean that a graph "runs"?
     
  7. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,833
    Notice that PraetorBlue's sample code does not divide by the end value of the loop. It divides by 100--because it wants to increment the float value by an amount of 1/100 per step. You don't need to know in advance how many steps there are going to be, only the step size.



    To add a little more detail on the floating-point-error issue, the reason you are accumulating errors so quickly is because the number 0.01 cannot be represented in binary. You know how some simple fractions result in infinite repeating decimals, like 1/3 = 0.333333....? In base 10, the only terminating decimals are the fractions whose denominators contain only the factors 2 and 5 (the prime factors of 10)--1/2, 1/5, 1/10, 1/4, 1/25, etc. In base 2, the only terminating "decimals" are fractions whose denominators are powers of 2. Fractions like 1/10 or 1/100 give infinite "decimals" and so can't be written down.

    So when you write 0.01f, the computer actually picks the closest number to 0.01 that can be stored in a float. When you print it, it looks like 0.01, but that's only because the computer is rounding when converting it to a string; if you ask for enough precision in that conversion (using the appropriate numeric format specifier), you'll discover that 0.01f actually isn't 0.01, even before you do any math on it.

    If you switched from an increment of 1/100 to an increment of 1/128 = 0.0078125, then the step value could be represented exactly in binary, and you won't get errors right away. 1/128 + 1/128 equals exactly 2/128.

    If you keep doing i += 1/128f, you will eventually run into a different problem, which is that the float format uses "scientific notation" where some of the bits are used for an exponent. This allows the float data type to represent very big numbers, and very small numbers, but not at the same time--when a number gets big enough, you start losing precision off the bottom. At some point, your total will become so big that your tiny increment can't be represented anymore, and then it will stop growing because (i + 1/128f) == i

    But until you get to that point, I believe you can keep doing i += 1/128f and you won't get any rounding errors.
     
    Joe-Censored likes this.