Little-known details of the implementation of Math.Round() in .Net

It all started with the fact that clients informed us about a discrepancy of a penny in the report. The program rounded the value 7.145 to 7.14. I checked all the calculations on the calculator, got 7.145, and was glad that at least the bug was repeating itself.

The first suspicion fell on the use of incorrect data types (float or double instead of decimal). I rechecked the code again and didn't find any problems. I created a new unit test for this situation, ran it, and the test quite expectedly failed. Then I launched the debugger and began to look at all the stages of the calculation (it uses a rather confusing formula, the value is rounded at the very end, I decided to check what happened before rounding). Before rounding, I see a completely correct value of 7.145. I can’t believe my eyes, I enter the value into the watch window, check the type again (Decimal, everything is as it should be), add rounding in the watch window, I get 7.14. I pinched myself just in case and began to substitute different values ​​into Math.Round with my hands, checking what else could be wrong. When 7.155 quite expectedly rounded up to 7.16, and 7.165 also rounded up to 7.16, I began to suspect that it wasn’t me who was going crazy. I went to MSDN and found…

How rounding is implemented in .Net

Math.Round supports two rounding modes:

  1. System.MidpointRounding.AwayFromZero is the usual rounding method, +0.5 rounds to 1 and -0.5 rounds to -1. Those. towards a larger modulus.

  2. System.MidpointRounding.ToEven – rounds to even, 0.5 is rounded to 0, and 1.5 is rounded to 2.

The Math.Round method itself has several overloaded options: for Double and for Decimal, with and without indicating the number of decimal places in the result, with and without indicating the type of rounding.

The most interesting thing is that the default rounding algorithm is System.MidpointRounding.ToEven. MSDN, of course, writes about this, but personally, for example, I think that the principle “if all else fails, finally read the instructions” is quite logical and I follow it to the best of my ability.

After rummaging around on the Internet, I collected information about

Why do we need multiple rounding modes?

MSDN is limited to the remark “It conforms to IEEE Standard 754, section 4.” (It complies with IEEE 754, section 4.) The standard itself is devoted to the general representation of fractional numbers in computer memory and does not contain much information about why these rounding modes are needed. Moreover, only the current version from 2008 contains a mention of the rounding mode “to even,” but the previous one (from 1985) was not yet aware of it. Wikipedia in article about rounding calls rounding to even “banking” and talks about the problem it solves.

If you take a sequential series of numbers (making the assumption that the input values ​​to the rounding function are distributed more or less evenly and randomly), then with normal rounding there is a cumulative rounding error. Below is an example that I sketched out in Excel.

As you can see, at 20 numbers the rounding mode we are used to gives an error of 1 (almost 5%). For me personally, this once again reminded me that “self-evident conclusions” are usually incorrect – in my everyday consciousness I always believed that in 5 cases rounding occurs to 0 and in 5 cases to 1, so the sums of the original sequence and the rounded one should converge. This is wrong.

Another interesting story about this rounding mode was told to me by the same client who reported this bug. Previously, she worked as a mathematics teacher at school for a long time, and about 30 years ago she saw the moment when they changed the curriculum and, in particular, the teaching of rounding rules. Until that moment, at school they had given rounding “to even”, and then everyone was given a simpler and less correct scheme. The Soviet school was then ahead of the American engineering community.

Lessons Learned

To be honest, after diving into the problem, I was a little discouraged 😉 In my opinion, the .Net developers have buried a stink bomb here. This behavior does not fit into the general scheme of other Microsoft products at all.

  • The ROUND function in MSSQL can only round in the usual “from zero” mode.

  • The ROUND function in MS Excel can only round in the usual “from zero” mode.

  • MS Excel has an ODD function that rounds “to even”, but it only rounds to integers, the precision cannot be specified.

  • The Math.Round function in .Net suddenly rounds “to even” by default. Slightly less than all programmers use rounding without specifying the mode, and slightly less than all clients, when checking a product, check it with calculations in Excel, where they use ROUND. Fun with particularly meticulous clients is guaranteed.

  • There is no mechanism for global control of rounding (such as locale settings, date format, for example) in .Net.

For ourselves in the product, we decided that the error is small and rare, it’s easier to do rounding like in Excel than to explain to everyone that they have been calculating incorrectly all their lives. We moved the rounding logic into our own wrapper class, which can be controlled through client settings. If some client is fundamentally correct, he can turn on bank rounding and not fool the others.

For dessert

A little later, another bug report about rounding came from the same clients.

If you calculate on a calculator, then 1.58*25%=0.395 should be rounded to 0.40 in Excel, but this does not happen. If you enter a number by hand, it is rounded correctly. In this case, of course, the error occurs because the formulas in Excel are calculated using “imprecise” data types. If you show more signs, the picture becomes more clear.

Everything seems clear and explainable, but trust in Excel has noticeably decreased. It turns out that the tool that the “ordinary consciousness” perceived as a source of reference data to which the program results should converge is no longer such.

The moral of the whole story is this:

Dear colleagues, be careful and careful!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *