

How .NET's Math.Round has Nothing to do with Maths. And That's OK!
source link: https://www.codeproject.com/Tips/1272542/How-NETs-Math-Round-has-Nothing-to-do-with-Maths-A
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Introduction
Before we start, imagine what values "a
" and "b
" would have after running the following:
a = Math.Round(1.5); b = Math.Round(2.5);
If your answer is "a = 2
and b = 3
", you are, just like I was, wrong. The correct answer is that both a
and b
end up becoming 2
. Confused? You might want to stick around for the reason why.
Background
About a week ago, I was working on some code that was moving two entities along the x-axis. Inside the library, their x
value was a double
, but the public
interface only allowed for meters in the form of an integer. I used Math.Round
to translate the double
value into that integer.
One of the requirements for the code was that the entities could never be closer than one meter to each other. I used the rounded value to check this.
As you might have guessed by now, the code kept failing. Even though both entities started out exactly one meter apart, had the same starting speed and were accelerating with the same speed, using the same algorithm, the integer value kept returning the same x
value once in a while and thus the code kept failing.
At first, I thought it was some kind of precision error as a result of using double
values. But this wouldn't make much sense, as the algorithm would result in the same precision loss in both values. After some debugging, I eventually found out it was not the double
but Math.Round
that was behaving differently than I had anticipated.
Bankers Rounding
After I had figured out the problem was with the use of Math.Round
, I quickly figured out the problem: the default[1] implementation for rounding in .NET is "Round half to even"[2] a.k.a. "Bankers Rounding". This means that mid point values are rounded towards the nearest even number. In the example I provided in the introduction, this means both values are being round towards 2
, the nearest even number.
So why was it implemented like this in .NET? Is it a bug?
Well, when first implemented, Microsoft followed the IEEE 754 standard. This is the reason of the default Math.Round
implementation.[3] (Note: The current IEEE 754 standard contains five rounding rules[4])
Another good reason is that bankers rounding does not suffer from negative or positive bias as much as the round half away from zero method over most reasonable distributions.
Round Half Away From Zero
But all is not lost.
If you expect 1.5
to be rounded to 2
and 2.5
to be rounded to 3
(like I expected), the Math.Round
method has an override that allows you to use the "Round half away from zero"[5] method instead.
a = Math.Round(1.5, MidpointRounding.AwayFromZero); b = Math.Round(2.5, MidpointRounding.AwayFromZero);
In the code snippet above, 1.5
is rounded to 2
and 2.5
is rounded to 3
.
Round Half Up
Interestingly however, after doing some research, it looks like "Round half away from zero" is also not the method commonly used in maths. Instead, the "Round half up"[6] method is usually used[7] in maths. For positive numbers, there is no difference, but for negative numbers, mid point values are rounded towards +∞ instead of away from 0.
This way, there are the same amount of fractions being rounded to zero, while in the "Round half away from zero" method, both 0.5
and -0.5
are not rounded to zero, making zero an exception to all other numbers.
Math ≠ maths
So it turns out that the Math.Round
method does not even support the actual rounding method that is commonly used in maths.
When I first discovered this, I was quite disappointed. After all, even with the good reasons that were provided, the library is still called Math
. It does seem to communicate a certain intent.
But writing this article did make me think: before looking into it, I was not even aware there were so many rounding methods. Each with its own pros and cons. Each with its own consequences. If I didn't know about all these methods, I certainly did not know about those consequences. So maybe it is not such a bad thing that someone else did this for me, and made the appropriate "default" decision.
After all, if the consequences of your rounding are that critical to your code, I do hope that you don't make the assumptions I did, or at least discover they were wrong with some good old fashion tests.
Wrapping Up
Even though this subject isn't really brain surgery (or rocket science for that matter), I do hope you learned something new, or at least enjoyed the read.
In the end, I think this is a good reminder of how complex even the seemingly simple things we use every day can be. And there is probably a lesson about assumptions in here as well. ;)
Happy rounding!
Other Programming Languages
After discussing this subject with others, I got curious how other languages handle the mid point values. I was happily surprised that almost every language did have this subject covered. However, there are a lot of differences between languages.
To hopefully help someone in the future, I have mapped the rounding methods in a table. X's in bold are default implementations, other X's are optional parameters or separate methods.
In an attempt to not clutter the references list, the language names in the table link to the documentation I used.
Without further ado, the big "programming language/rounding method table":
* The table references Python 3.7 but Python 2 actually has a different round implementation. It will round half away form zero.
** The mode keywords in Ruby are :up
for "round half away from zero" and :down
for "round half towards zero", making them quite confusing in my opinion.
As I have no experience with most of these languages, feel free to point out any mistakes. I will correct the table accordingly.
References
History
- 23-12-2018 - Version 1
- 23-12-2018 - Version 1.1
- Minor textual changes
- 24-12-2018 - Version 1.2
- Clarified example
- 26-12-2018 - Version 1.3
- Minor textual change
- 05-01-2018 - Version 2.0
- Added chapter "Other Programming Languages"
Recommend
-
63
One day I'll get all the project managers I can find in a room and give them a GCSE maths paper to complete. Every five minutes I'll tap them on the shoulder and say "Can you stop what you're doing and move onto question 10?"...
-
44
Noseman is having a headache and as an old-school hypochondriac he goes to see his doctor. His doctor is quite worried and makes an appointment...
-
24
Proof is the essence of mathematics. Any mathematical result should be derived from first principles using a watertight chain of logical reasoning. Proof is what separates mathematics from other intellectual endeavours,...
-
16
Ability to perform symbolic computations is a crucial component of any mathematics-oriented package. Symbolic mathematics is used to work with complex expressions, sets and probabilities, perform integrals or derivatives...
-
14
Garbage collectors for the busy Java developer (10 min, no maths ;)) java-version.com: What's new in Java 16? 15? Keep up to date! Garba...
-
11
MathJax, at last a decent way to post maths on the web Jan 11, 2012
-
9
[Math] IEEE 754 round-off errors[Math] IEEE 754 round-off errors (The following text has been copypasted to the Math Recipes book.) The example from Wikipedia This fragment from...
-
7
Java Math.round() with Examples Oct 12, 2019 · 2 mins read The java.lang.M...
-
7
Rounding in Java – Math.Round, Math.Floor, Math.CeilWe have decimal values in java, but sometimes there is a need to round them. It is important to remember which function should be used according to the requirements. So In...
-
6
The New CSS Math: round() Aug 17, 2023 CSS added many new Math functions to supplement the old favorites (think calc() and the more recent clamp()). They all ultima...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK