I’m bad at explaining so I’ll give an example.
I want to round see if a float is closer to X.15 or X.65 and round to the nearest one. I can round to one, but how do I see which one is closer and round to that?
I’m bad at explaining so I’ll give an example.
I want to round see if a float is closer to X.15 or X.65 and round to the nearest one. I can round to one, but how do I see which one is closer and round to that?
How about Mathf.Round?
Calculate the difference between x and x low, compare that to the difference between x and x high and use whichever difference is smaller.
The difference is just b - a, so it’s something like if( x - .15 < x - .65) then x = .15 else x - .65.
Rounding is pretty standard.
Two quick notes:
If you are meaning the much simpler idea that you have 2 static values like 1.15 and 1.65 and you always want to round to those two specific numbers (eg. 9.3 will round to 1.65) then the answer is :
public float RoundTo(float min, float max, float val)
{
return Mathf.Min( Mathf.Max(val,min),max));
}
However if you mean the more complicated idea that you always round to the nearest X.15 and X.65 example 9.5 would round to 9.65 or 8.1 would round to 8.15 then indeed @Kiwasi algorithm is correct, except for one small detail. His algorithm will get you the Floor of that expression rather than the round. So 9.7 = 9.65 but 9.5 = 9.15. Just change his (Cast to an Int) to Mathf.Round
Note, this is a streamlined algorithm for a more complicated process. Say you want to round between X.1 and X.8. Bored Mormon’s algorithm (fixed to rounding) will only work if the range is 0.5. Thats because the range is .5 whether its in between the two numbers, or its outside of them. In our case the inner range is .7 while the outer range is only .3. If you need a custom round between any two values here is a script you can just add to a project doesn’t need to be on a GameObject:
Custom Round
using System;using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class MathTools {
public static float CustomRound(float min, float max, float val)
{
float normalMin = min - Mathf.Floor(min);
float normalMax = max - Mathf.Floor(max);
if (normalMin > normaMax)
{
string exString = "Min: " + min.ToString() + " is greater than Max: " + max.ToString() + " in CustomRand";
throw new ArgumentException(exString);
}
float innerRange = normalMax - normalMin;
float outerRange = 1 + normalMin - normalMax;
float wholePart = (float)Math.Floor(val);
float fracPart = val - wholePart;
if (fracPart < normalMin || fracPart > normalMax)
{
int carry = fracPart > normalMax ? 1 : 0;
fracPart += (1 - normalMax);
if (fracPart >= 1)
fracPart--;
fracPart /= outerRange;
fracPart = (float)Math.Round(fracPart, MidpointRounding.AwayFromZero);
if (fracPart == 0)
fracPart = normalMax-(1-carry);
else
fracPart = normalMin + carry;
}
else
{
fracPart -= normalMin;
fracPart /= innerRange;
fracPart = (float)Math.Round(fracPart, MidpointRounding.AwayFromZero);
fracPart *= innerRange;
fracPart += normalMin;
}
return fracPart + wholePart;
}
}
You would call it from another script like this:
float val = 6.4;
float rounded number = MathTools.CustomRound(.5f,.8f, val);
This will return 6.5
Note the min and the max rounds don’t have to be 0.xxx You can give it bigger numbers and it will strip the leading digits off like so:
float val = 5.1;
float number = MathTools.CustomRound(2.5f,2.9f,val);
This will return 4.9
Explanation of Algorithm
The idea behind this algorithm is we want to use Math.Round which rounds a number in the range of 0.0 to 1.0. So we need to normalize our values onto [0.0,1.0] We do this by first stripping off the leading integers so we are just left with 2 floats 0<=min<=max<1 First we needto adjust the range [min,max] so it starts at 0. We do this by subtracting min. Now we have [0,max-min] as our range. The actual value this range spans is (max-min) - 0. Which is just max-min. We will call this RangeSPan If we divide any number in this range by RangeSpan it will transform it into the [0,1] range and now we can use regular old Math.Round. We will now end up with either 0 or 1. Which we convert back to our range by first multipy by RangeSpan. this will give us either 0, or (min-max). If we add min back to this value we end up with either min or max, our rounded value! The last part is just to add the integer portion back in.
If the fractional portion of the number isn’t in between min and max we follow the same algorithm above. Its slightly more complicated to get a range from [0,RangeSpan]. and we have to keep track of a carry operation in case we go up to the next integer or back down to the previous one.
Note if RangeSpan , which will always be between 0 and 1.0 can be expressed as a fraction 1/q where q is any whole number, then we don’t need to remove the integer portions first. Because they will always be multiplied out to another integer, and back to their original integer in the process. However if the RangeSpan is a number like .6 = 3/5 we can’t keep the integers in because they will not map to other integers. And their fractional parts will get lost in the normalization of the RangeSpan, as well as adding to the fractionalPart thus giving false answers in both the rounding, and the unwound integer number.
Additionally if the range is exactly 0.5 its the same for both parts of the algorithm. So we don’t need to worry about inner and out ranges. Which is why @Kiwasi algorithm is the same as mine just super optimized. 0.5 = 1/2 so we can keep the integers and we don’t have to worry about inner or outer ranges.
@takatok Thanks testing it now, fingers crossed!
Awesome it works! Thanks so much for taking the time to write a long post. I really appreciate it and I’ll definitely be using it in the future ![]()
Also thanks for the explanation, but I’m too dumb to understand it ![]()
The simple idea is this. Say you have an number between 0 and 0.6 If you divide it by 0.6:
So you can see diviing it by how “big it is” will always get you a number between 0 and 1. When you have a number between 0 and 1 you just regular Math.Round. Now that will end up giving exactly 0 or exactly 1. Just multiply that answer by your original 0.6 to get Exactly 0 or Exactly 0.6 Now you’ve rounded it between 0 and 0.6.
The tricky part is getting it to the 0->0.6 range to begin with.