0

I'm trying to use Math.Round however the results I'm getting are not what I expected. I'm finding that it only rounds to even when the following digit is a 5 and cannot have any other digits after that.

Assert.AreEqual<double>(4d, Math.Round(4.5));          // Pass
Assert.AreEqual<double>(5d, Math.Round(4.6));          // Pass
Assert.AreEqual<double>(6d, Math.Round(5.5));          // Pass
Assert.AreEqual<double>(4d, Math.Round(4.500001));     // Fail - 5
Assert.AreEqual<double>(4d, Math.Round(4.45));         // Pass
Assert.AreEqual<double>(4.4, Math.Round(4.45, 1));     // Pass
Assert.AreEqual<double>(4.4, Math.Round(4.450001, 1)); // Fail - 4.5 

The behaviour is also the same for Decimal.Round.

Assert.AreEqual<decimal>(4m, Decimal.Round(4.500001m));      // Fail - 5
Assert.AreEqual<decimal>(4.4m, Decimal.Round(4.450001m, 2)); // Fail - 4.5

Shouldn't rounding only take into account the digit directly following the decimal place you're rounding to, as described in MidpointRounding?

A rounding operation takes an original number with an implicit or specified precision; examines the next digit, which is at that precision plus one; and returns the nearest number with the same precision as the original number.

kjbartel
  • 10,381
  • 7
  • 45
  • 66
  • `Math.Round` rounds the number to the nearest integer. If the number is halfway between two integers the even one is returned. `.5` is halfway between two integers, `.500001` is not. – RobH Feb 20 '14 at 08:18
  • Er... 4.500001 is closer to 5 than to 4. I don't see why it would ever make sense to round that to 4. –  Feb 20 '14 at 08:19
  • I partly blame the documentation for `MidPointRounding`. It's inaccurate to say the least (I've just submitted feedback on this) – Damien_The_Unbeliever Feb 20 '14 at 09:06
  • @Damien_The_Unbeliever I've added a link to that documentation in the question for other people's reference. The below answer is correct though that logically only the exact mid point is when it rounds to even which means all digits are examined and not just the "next digit" in the case of ToEven rounding. – kjbartel Feb 20 '14 at 10:47

1 Answers1

2

The behaviour is correct. You are arguing that 4.50001, 4.51, 4.59, 4.599999999999 should all round down to 4. It is clear that figures over 4.5 are closer to 5 than they are to 4 and therefore should be rounded up to 5.

Josh Gallagher
  • 5,211
  • 2
  • 33
  • 60
  • Well that does make sense logically. Bit of a problem if you happen to do a calculation which adds a bit of garbage to your value. Guess that's why they call it a rounding error. – kjbartel Feb 20 '14 at 08:50
  • Are you saying there is some inaccuracy you'd like to remove prior to doing the rounding? – Josh Gallagher Feb 20 '14 at 09:12
  • To be fair to the OP, if you take the documentation for [`MidpointRounding`](http://msdn.microsoft.com/en-us/library/system.midpointrounding(v=vs.110).aspx) at face value, that is what it says: "if the next digit is 5, which is the midpoint between two possible results, the nearest number is ambiguous" - not "if the next digit is 5 and all remaining digits are 0" which is the actual ambiguous case. – Damien_The_Unbeliever Feb 20 '14 at 09:16
  • To add to my previous comment, if the garbage being added is always positive then you could consider using "Math.Floor(myValue * 10)/10" to 'chop off' the extra bit. Otherwise you could choose to do a double rounding operation, firstly to 1 dp and then to 0 dp. It all depends on how your values are being generated. – Josh Gallagher Feb 20 '14 at 09:18
  • @josh Take for example this: Assert.AreEqual(0.044, Math.Round(4.45 / 100, 3)); – kjbartel Feb 20 '14 at 09:20
  • @Damien_The_Unbeliever Which doc are you looking at? I was trying to find detail in the IEEE 754 section 4 due to it being referenced in the MSDN doc, but it just talks about 'nearest'. – Josh Gallagher Feb 20 '14 at 09:20
  • @JoshGallagher - the documentation for MidpointRounding itself (which I've linked to in the previous comment) - paragraphs 2 and 3 in the remarks are all phrased in terms of just "the next digit" – Damien_The_Unbeliever Feb 20 '14 at 09:23
  • @kjbartel You should assume double arithmetic will be inaccurate. The Shouldly unit testing library has methods that acknowledge this, such that you can write: "double d = 3; d.ShouldBe(3, 0.001);" where the 0.001 is the tolerance of the check. – Josh Gallagher Feb 20 '14 at 09:23
  • @Damien_The_Unbeliever That is exactly why I asked whether it only takes into account the single digit and not all others after that too. Sure it seems pretty obvious that 0.59 is closer to 1 than 0... but the documentation does only mention the single digit. – kjbartel Feb 20 '14 at 09:28
  • @Damien_The_Unbeliever Sorry, missed that! Yes, I see what you mean. That documentation is misleading. It talks about "the digit after" as if it is only necessary to look at that digit and ignore others after. I also found this [Math.Round](http://msdn.microsoft.com/en-us/library/wyk4d9cy(v=vs.110).aspx) link that explicitly states that rounding with doubles might not work as expected. See the Note to Callers. – Josh Gallagher Feb 20 '14 at 09:29
  • @Josh I think one of the main use cases for rounding is to try and remove arithmetic errors in floating point numbers. Probably even more so in the case when specifying the number of decimal places. Using decimals does avoid the arithmetic errors but it is quite a bit slower when crunching a lot of data and of course it doesn't have anywhere near the same range. I think I'll be using decimals for the time being and see if performance ends up being a big issue or not. – kjbartel Feb 20 '14 at 09:39