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

Rounding away from zero, instead of to nearest even.

Discussion in 'Scripting' started by Chrisenium, Jul 24, 2019.

  1. Chrisenium

    Chrisenium

    Joined:
    May 17, 2013
    Posts:
    11
    Hi,

    I'm making a short educational maths game about rounding numbers. In my game I have enemies who get assigned a random float number based on a few constraints (what place is the number to be rounded to, and how long should the number be). The students then cast either a round up spell or a round down spell on the enemy based on which is correct.

    I generate a random float (based on the above constraints), then I trim it to the desired amount of numbers past the decimal, then I calculate the "roundedNumber" and subsequently check if the rounded number is higher than the startingNumber to see if the student should round it up or down.

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class Number : MonoBehaviour
    4. {
    5.  
    6.     [HideInInspector] public bool roundUp = false;
    7.  
    8.     private float startingNumber;
    9.     private float roundedNumber;
    10.  
    11.     private void Start()
    12.     {
    13.         startingNumber = Mathf.Round(Random.Range(11f, 94f));
    14.         roundedNumber = Mathf.Round(startingNumber / 10f) * 10f;
    15.         if (roundedNumber > startingNumber) roundUp = true;
    16.     }
    17. }
    This generally works fine, except in Unity Mathf.Round goes to nearest even number, so if my random number is 84.999, it first rounds it to 85 (which I want), and displays to my students the number 85. They should then round the number 85 up to 90, but as Unity goes to nearest even, it says to round it down. Is there any alternative where it rounds away from zero instead of to nearest even when it's at 5 or 0.5 etc.?
     
  2. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,835
    Rather than using Unity's Mathf.Round(), have you considered using plain old C# System.Math.Round()? It looks like it has a parameter where you can specify what kind of midpoint rounding you want.

    However, also see their remarks about how midpoint rounding can give counter-intuitive results due to floating-point precision limitations.

    This may be a situation where it would be better to avoid floating-point types and instead roll your own "fixed point" format, by storing your fractions as ints, but with the convention that you insert a decimal point N positions from the end whenever you print them on the screen. e.g. an int value of 1234 in code actually represents an on-screen number of 1.234 or 0.001234.
     
    Last edited: Jul 24, 2019
    Chrisenium, lordofduct and SparrowGS like this.
  3. Chrisenium

    Chrisenium

    Joined:
    May 17, 2013
    Posts:
    11
    Oh cool, I didn’t even know it was possible to just put a decimal point somewhere in a number like that. How do you actually do that?
     
  4. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,748
    Well the simplest way is, you do all the math relating to that number as an int, but when you go to display it, you can do something like:
    Code (csharp):
    1. int myNumberWithTwoDecimals = 1234;
    2. float myDisplayNumber = (float)myNumberWithTwoDecimals * 0.01f;
    3. Debug.Log(myDisplayNumber + " should be 12.34");
    There's probably a way to construct a string without the intermediate float, but this is simple and easy to remember.
     
  5. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,835
    There isn't a "built in" way to do this (AFAIK), but computers will do pretty much anything you want as long as you can define mathematically precise steps for them to follow.

    One way to do this is to break your integer into pieces using integer division and remainder; e.g.
    Code (CSharp):
    1. int num = 12345;
    2. int wholePart = num / 100;
    3. int fractionalPart = num % 100;
    4. string numAsText = wholePart.toString() + "." + fractionalPart.toString("D2");  // D2 adds leading zeroes, if necessary, to ensure the number prints with 2 digits
    5. Debug.Log(numAsText);  // 123.45
    You could even convert the entire number to a string and then use string operations to insert the decimal point; e.g.
    Code (CSharp):
    1. int num = 12345;
    2. string rawString = num.toString();
    3. string frontPart = rawString.Substring(0, rawString.Length - 2);
    4. string endPart = rawString.Substring(rawString.Length - 3, 2);
    5. string combinedString = frontPart + "." + endPart;
    6. Debug.Log(combinedString);  // 123.45
    And of course, you can wrap up all this logic in your own helper function so that it becomes a one-line call.

    If you want to use StarManta's approach of converting the number to a float when displaying it, you should probably use a format specifier to control the number of digits it prints with when you convert that float to a string. Since there's still a step involving floating-point variables in this approach, you could theoretically still run into precision issues, though for realistic use-cases it's probably not an issue as long as you're only doing it in the final step when you convert to a string.


    Also, if you're going to use this approach, it's important to remember that you'll need a corrective factor any time you multiply two numbers. For instance, if you have the number 1.01 and 2.00 encoded internally as 101 and 200, then when you multiply them the computer will give you 20200 (translating to 202.00 in UI) when you actually want 202 (translating to 2.02 in UI), so you'll need to divide by 100 to correct the "inflation".

    If you were going to write a really complex program using this convention, you'd probably want to write your own class to represent your "fixed point" numbers, and write a collection of functions and operators for that class that "hide" these details and let you just use * or .toString on them as if they were regular numbers.

    You might even be able to such a class that someone has already written, but I don't know of one off-hand.
     
  6. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,835
    By the by, if the ultimate point of all of this fractional stuff is just so that you can round to a multiple of 10, you would probably be better off just writing your own round function; e.g.

    Code (CSharp):
    1. // Rounds the first argument to the nearest multiple of the second argument,
    2. // rounding up if it's exactly halfway between two options
    3. // IMPORTANT:  Only works for nonnegative numbers!
    4. // Negative numbers left as an exercise for the reader.
    5. public static int RoundToMultiple(int num, int toMultipleOf = 10)
    6. {
    7.     int remainder = num % toMultipleOf;
    8.     if (remainder * 2 >= toMultipleOf)
    9.     {
    10.         // Round up
    11.         return num - remainder + toMultipleOf;
    12.     }
    13.     else
    14.     {
    15.         // Round down
    16.         return num - remainder;
    17.     }
    18. }