Skip Navigation LinksHome > View Post

Bankers rounding

It is not often I write unit tests which test features of the .NET framework, but a colleague of mine pointed out this particular curiosity about the decimal.Round() method. Consider the following unit test below:


[TestMethod]
public void RoundingTest1()
{
    decimal dec1 = 1.134M;
    decimal dec2 = 1.135M;

    int decimalPoints = 2;
    Assert.AreEqual(1.13M, decimal.Round(dec1, decimalPoints));
    Assert.AreEqual(1.14M, decimal.Round(dec2, decimalPoints));
}

This test passed, which was what I expected: Rounding the first decimal down and rounding the second decimal up.

Now consider the following very similar test:


[TestMethod]
public void RoundingTest2()
{
    decimal dec1 = 1.124M;
    decimal dec2 = 1.125M;

    int decimalPoints = 2;
    Assert.AreEqual(1.12M, decimal.Round(dec1, decimalPoints));
    // Fails, as it rounds down.
    Assert.AreEqual(1.13M, decimal.Round(dec2, decimalPoints));
}

This test actually fails, and it is the second statement which fails by also rounding down. At first I was a bit baffled by this behaviour but reading the documentation to the decimal.Round() method closer it actually says:

Return Value
The integer that is nearest to the d parameter. If d is halfway between two integers, one of which is even and the other odd, the even number is returned.

So it turns out that my assumption in the second test is actually wrong, and with a some more investigation I found that the decimal.Round function provides an overload which takes a third parameter of type MidpointRounding Enumeration.

This enumeration contains the following values:

  • AwayFromZero - "When a number is halfway between two others, it is rounded toward the nearest number that is away from zero."
  • ToEven - "When a number is halfway between two others, it is rounded toward the nearest even number."

In essence, the decimal.Round() method defaults to rounding with the same behaviour as if you would pass it the MidpointRounding.ToEven as the third parameter. Modifying my second test as shown below made this one pass too.


[TestMethod]
public void RoundingTest2()
{
    decimal dec1 = 1.124M;
    decimal dec2 = 1.125M;

    int decimalPoints = 2;
    Assert.AreEqual(1.12M, decimal.Round(dec1, decimalPoints, MidpointRounding.AwayFromZero));
    Assert.AreEqual(1.13M, decimal.Round(dec2, decimalPoints, MidpointRounding.AwayFromZero));
}

Rounding using MidpointRounding.ToEven is often called unbiased rounding or statistician's rounding or bankers' rounding and the .NET rounding functions default to this behaviour. Definitely one to look out for!

Tags: .NET

 
Bruusi Post By Bruusi
12:38 AM
20 Apr 2007

» Next Post: Failing unit tests are devaluing your working tests
« Previous Post: Logging Levels and how to use them

Comments are closed for this post.

Posted by Chris @ 13 Mar 2008 2:04 PM
How about the following?
Try Math.Round(.285,2,AwayfromZero)
The Result Should be .29, but you get .28
From: http://blogs.claritycon.com/blogs/don_peterson/archive/2006/04/16/398.aspx

© 2005 - 2014 Josh Twist - All Rights Reserved.