MidpointRounding.ToZero doesn't work in Unity

I need to use different midpoint rounding rules than the default “round to even” Mathf.Round() has. Mathf.Round() doesn’t have parameter for midpoint rounding, so I’m using System.Math.Round().

C# has different strategies for midpoint rounding, specified here: MidpointRounding Enum (System) | Microsoft Learn . MidpointRounding.AwayFromZero and (obviously) MidpointRounding.ToEven work in Unity, but others, namely MidpointRounding.ToZero don’t work, for some reason (“CS0117: ‘MidpointRounding’ does not contain a definition for ‘ToZero’”).

float firstN = ...
float lastN = ...
int begin = (int)Math.Round(firstN, MidpointRounding.AwayFromZero); //works
int end = (int)Math.Round(lastN, MidpointRounding.ToZero);          //doesn't work

I absolutely need to use MidpointRounding.ToZero, there really isn’t any alternatives. I would have to rewrite big parts of the code again. Writing my own rounding function just to solve this one problem doesn’t sound fun either.

Seriously? Unless I misunderstand your actual definition of round, it sounds trivial.

Not only that but it sounds like the only way you can be confident.

Challenge: what takes longer, writing MathPaskapytty.Round() or making a forum post?

I’m not sure why you’re getting that particular problem, but it should be noted that if you need precise control over your rounding rules, you often shouldn’t be using floating-point numbers at all. float math often gives you a result that’s a little bit different from what you’d intuitively expect; for example 0.1 + 0.2 != 0.3.

In situations where precision is vital, a common strategy is to use “fixed-point” representations (i.e. use an int, but pretend that a value of 1 in your variable really represents 1/100 or something).

2 Likes

Well, if I was that skilled, I would have already done it. There’s a reason people ask questions.

If it is trivial, why didn’t you write it? Your answer doesn’t help me at all.

2 Likes

Thanks! Yes, I’ve realized that. But I’m dealing with x and y coordinates and intersections of lines, and specifically if the line is in 45 degree angles (perfectly splitting tiles in halfs, that’s why I need midpoint rounding rules). I need to calculate the intersection coordinates, so using integers for arbitrary coordinates isn’t really possible. And the precision of floating point is enough for me in this case. Also, I’d rather use an intended feature of c# than write a whole thing of my own.

I don’t see where the midpoint rounding enters into that problem. What number is being rounded?

Also, checking if something is at a 45 degree angle is exactly the sort of problem where you probably want to test for approximate equality, because otherwise you’ll discover that two lines that “should be” at a 45 degree angle turned out to be at a 45.000000000000001 degree angle due to floating point rounding errors.

You’ll find that simply casting a float to an int actually does a ToZero type of rounding.

         for (float f = -2.0f; f <= 2.1f; f += 0.20f)
        {
            int x = (int)f;   // implicit .ToZero style of rounding
            Debug.Log( f.ToString("F2") + " rounds to " + x);
        }

Just beware that this style of rounding (ToZero) causes discontinuities around zero in the rounded numberspace: everything greater than -1 and less than 1 (a nearly-complete span of 2 in the real number domain) all maps to zero.

In other words the rounded output has a double-wide flat spot around zero. This is usually not desirable, especially when calculations span an arbitrary negative to positive space.

1 Like

I now realized my mistake that MidpointRounding.ToZero, despite being tacked on to the MidpointRounding enum isn’t doing what I thought it did. I thought it was mirror of what MidpointRounding.AwayFromZero does, rounding “normally” but rounding halves towards zero. At least it would have made sense. But yeah, it’s not. I do need to have my own rounding function after all. Thanks for the replies!

Mathf.Floor() will essentially always just lop off everything after the dot, leaving everything before the dot.

https://docs.unity3d.com/ScriptReference/Mathf.Floor.html

It’s probably the most generally-useful for having a continuous output space.

Yeah. It works basically like Floor().

You linked the midpointrounding enum documentation above, though have you actually read the remarks sections? It contains examples for the two categories (“Round to nearest” and “Directed rounding”).

ToEven and AwayFromZero belong to the first category while ToZero, ToPositiveInfinity and ToNegativeInfinity belong to the second. So ToZero does not “round to nearest” but is a directed rounding. So no matter what value you have it’s always rounded towards zero.

I don’t quite understand what you mean by

Are you sure you understood what AwayFromZero does? How would the mirror behaviour look like in your opinion? The term “AwayFromZero” is only relevant for values that are exactly 0.5 and that’s what the name refers to. Values greater than 0.5 are rounded up to the nearest integer, values smaller than 0.5 are rounded down to the nearest integer. However what about 0.5 itself? Exactly, that’s what AwayFromZero means. So positive numbers that end exactly in .5 are rounded up while negative values are rounded down. It’s really only relevant for the exact .5 values. The same is true for the ToEven rule. Both round the same way, the only difference is how the exact .5 values are handled.

Just to make that clear, a value of 6.500001 would round to 7 with either ToEven or AwayFromZero and a value of 6.49999 would round down to 6 with both rules. The only difference is how the exact value of 6.5 is handled. ToEven would round it down to 6 while AwayFromZero would round it up. For most real ingame values this seperation is completely irrelevant because you barely will have exact .5 values and for any other values both behave the same way.

It would be interesting to know your exact usecase or a concrete example where it’s important that exact .5 values are always rounded towards 0. Your 45° examples doesn’t make too much sense as it’s already an integer ^^.

Anyways, you should be able to emulate a round to nearest with midpoint rounding towards zero like this

public static float RoundTowardsZeroMidpoint(float val)
{
    if (val > 0)
        return Mathf.Ceil(val-0.5f);
    return Mathf.Floor(val+0.5f);
}

Here’s a fiddle to show it in action.

Yes, I realized that later. My problem was that I couldn’t get it to work in Unity, but ultimately I was chasing the wrong rabbit, because I thought it worked differently.

I fully understood what AwayFromZero did. 2.1 = 2, 2.5 = 3 and 2.7 = 3. That’s what I wanted and it worked. But I thought ToZero did the same thing but to the opposite direction (since it’s under MidpointRounding, with AwayFromZero), i.e. 2.1 = 2, 2.5 = 2 and 2.7 = 3. But yeah, it rounds everything towards zero: 2.7 = 2. So yeah, wrong function for me. Weird it to be under MidpointRounding when it has nothing to do with midpoints.

My usecase is this:

I’m doing line of sight checks on a grid of squares. I draw a line from a center of a square to a center of another square and have to find every square it intersects along the way and check if there are any obstacles in those squares. Coordinates are fixed to the centers of the squares, so origin (0,0) is in the centre of a square and the right hand edge of that square is (0.5, 0), for example. I tried to look at different algorithms like Bresenham’s line algorithm, but I couldn’t use it, because I’m essentially “drawing” every pixel the line touches, if even slightly. So I did my own algorithm.

I’m calculating, column by column, which squares the line touches in that Y column. I calculate the intersection point the line enters the column and the intersection the line leaves the column (or the starting point and/or ending point of the line if we are at either end of the line). With those intersection points, I calculate which squares they belong to and then I will know the line goes through those two squares and every square in between in that column. Then I move on to the next column until the end of the line is reached

I calculate which squares the intersections belong to by rounding the Y value of the intersection point. If the line enters the column at, say (2.5, 1.2) and exits it at (3.5, 4.8), the Y values are rounded to 1 and 5 so I know the line touches the squares (3, 1) and (3, 5) and every square in between in that column.

So far so good. But, what about the case where the line enters the column at exactly the intersection of the squares, say (0.5, 0.5)? Is the line touching one or two squares when it enters or exits the column? It was a judgement call and I decided that the line touches only the square it enters to and/or leaves from when going through intersection of squares, it doesn’t touch the tangential squares. So only one square on each side.

Depending on the direction and angle of the line, I have to use different midpoint rounding rules, so for example a line going upwards, left to right, rounds midpoints up on the entry point and down on the exit point. That way both intersections are handled symmetrically and both intersections count only one square for that column.

And I don’t even have to calculate if the line goes through square intersections. Just by using correct midpoint rounding rules handles all cases, I don’t have to know exactly where the line enters a column or its angle. So yeah, the 45 degree angle was just an example, as there are of course infinitely many lines that can go through square intersections.

So that’s the gist of it. There really isn’t any way to use integers in this algorithm as the lines are arbitrary. Well, of course you could express the angles in ratios of integers or something, but KISS. I would guess this kind of algorithm has been done many times before, but I couldn’t find any that would fit my use exactly or ones that I’d understand. I suppose my algorithm isn’t the simplest or fastest, but at least it works and I understand it.

This is how my rounding function looks in the end. It can handle rounding to either direction. I also added a little check that if it’s really close to #.5 then it’s treated as being exactly #.5 (to combat floating point inaccuracies).

private int RoundMidpointTowards(float val, bool towardsZero)
{
    float cor = 0.5f;
    float valTail = Mathf.Abs(val - (int)val);
    if (valTail - cor < 0.001f)
        cor = valTail;
    return (towardsZero == (val < 0)) ? Mathf.FloorToInt(val + cor) : Mathf.CeilToInt(val - cor);
}

Suggestion: Instead of calculating the Y coordinates at X = 2.5 and X = 3.5, calculate them at X = 2.5 + epsilon and X = 3.5 - epsilon. (That is, slightly “inside” the column boundaries.)

That will cover you against midpoint rounding and cover you against floating-point precision errors, and you don’t need different cases for whether the slope is positive or negative.

Ooh, that sounds good! Epsilon would be something like 0.01f?

In this context, “epsilon” just means “a very small positive number”. Unity has a predefined “epsilon” constant in Mathf where I think they use the value 1e-5, but using .01f is also reasonable (assuming that your game never has to check line-of-sight from >50 squares away). Any number that’s small enough not to change your answer except for edge cases but large enough that it won’t get wiped out by floating-point precision rules.

I see, thanks!

FYI the real problem OP has run into is that enum value MidpointRounding.ToZero does not exist in .NET Standard 2.1. It was added in later .NET versions.

IMNSHO the real problem is reflexively reaching for a library to do a numerically specific and trivial operation such as a particular flavor of rounding.

If you are confident in the library’s function, use it.

If you’re not confident, write your own.

The general form for rounding anything to any interval is:

float input = ... however you get this...

input += midpoint;   // use 0.5f for "round up / round down" type behaviour

int i = (int)(input / intervalOrPerhapsOne);

int result = i * intervalOrPerhapsOne;