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

Mathf.RoundToInt not properly rounding multiplied decimals

Discussion in 'Scripting' started by powerhobo, Jun 17, 2020.

  1. powerhobo

    powerhobo

    Joined:
    Jul 17, 2015
    Posts:
    2
    I’m hoping (and fairly confident) I’m stupid.

    I have a float (trans) that gets adjusted by the user in increments of 0.1f, between 0f and 1f. It’s for image alpha, though I don’t believe that should matter.

    The float gets displayed to the user as an integer between 0 and 10. This seems like it should be an easy Math.RoundToInt(trans*10), and that does indeed work for 2-10 (0.2f - 1f float value). However, it will not display 1 (0.1f); it skips it and goes from 2 to 0 or vice versa.

    Printing the float value itself to the console shows that the increments aren’t exactly 0.1 anyway. 0.6 - 1 are on the money, but everything below that is actually 0.499 (rounds and displays as 5 as it should), 0.399, so on and so forth. But for whatever reason 0.99 rounds to 0.

    Am I making this harder than it should be? Should I instead have the user directly control the int value that is displayed and then use that with something along the lines of color.a = (int/10)f ?
     
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,735
    You may be surprised to hear that it is impossible to accurately represent the number 0.1 in a floating point, because 10 is not a power of two.

    I think it makes more sense to let the user control an int value from 0 to 10 and convert that to a float at the last second when you want to apply it to your color, like you said.
     
    Joe-Censored likes this.
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,385
    The fact the numbers aren't exactly 0.1 is, as @PraetorBlue mentioned, because of float error. 0.1 can't be represented accurately as a binary number. It's a repeating value. Think how 1/3 can't be represented in decimal because it's 0.3333... which is always just shy of 1/3 if you take a discrete version of it. And mind you, computers are always discrete, a float only holds 23(24 implicit) bits of significant values. The same goes in binary, 0.1 is 0.0001100110011001100110011...

    Thing is 0.1 * 10, though being off slightly should really be like 0.0999997 * 10 = 0.99... which should round to 1. The float error isn't that bad to screw with that rounding. (floors and ceils could act odd though)

    So instead I'd like to see your code to see what's actually happening to your numbers.
     
    Last edited: Jun 17, 2020
  4. powerhobo

    powerhobo

    Joined:
    Jul 17, 2015
    Posts:
    2
    First and foremost: thank you both for the information. I did not know that about floats, nor even realize that they were registering decimals significantly further than what was typed, so that's good to know.

    I am now doubly confused at this point, however. I copied my exact code (below) into a new project to strip if of unnecessary BS and add the simple variable print to the Start function, and it works just fine, rounding to 1-10 as it should. I proceeded to copy the exact stripped down code back into the project experiencing the shenaniganry, and it's skipping 1 again. I triple-checked, and there is nowhere else that these variables are being referenced that could interfere. I went full ham, and copied my entire unedited code into the new test project, and it's working fine.

    Code (CSharp):
    1.     private float trans = 1;
    2.     private int transDisplay;
    3.  
    4.     void Start() {
    5.         print(trans);
    6.     }
    7.  
    8.     void Update () {
    9.      
    10.         if (Input.GetKeyDown(KeyCode.LeftArrow) && trans >= 0.1f) {
    11.             trans -= 0.1f;
    12.             print(trans);
    13.             transDisplay = Mathf.RoundToInt(trans*10);
    14.             print(transDisplay);
    15.         } else if (Input.GetKeyDown(KeyCode.RightArrow) && trans <= 1.0f) {
    16.             trans += 0.1f;
    17.             print(trans);
    18.             transDisplay = Mathf.RoundToInt(trans*10);
    19.             print(transDisplay);
    20.         }
    21.  
    22.     }
    I was able to get it to work in my original project by changing the logic. I have a habit of using >= instead of just > (or <), so changing that to check that trans > 0 instead of trans >= 0.1 solved it. I can't really imagine why 0.99999993 would successfully register as 0.1 in one project but not the next, though.
     
  5. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    10,004
    Floating points are not deterministic due to the nature of the operations (there are ways to do that but those are expensive operations on scale and you end up using doubles instead so you hide the problem behind more bits). If you need fixed step you always need to use integers and calculate downwards if you need lower numbers.
    The same issue why you don't check float == with value. You check against difference with Epsilon or your own tolerance. And this is why Mathf.Approximately is a thing.