Thursday, February 17, 2011

Standard and Banker's rounding. C# vs Java

Decimal rounding is a pretty simple concept, isn't it? And I'd never expected to encounter anything unusual there. And indeed, lets take a look at Java:
System.out.println(Math.round(2.5));
System.out.println(Math.round(3.5));

The program output is:
3
4
Correct? Yes, it is exactly how we were teached at school at Soviet Union.
Now let's take a look at C#
Console.WriteLine(Math.Round(2.5));
Console.WriteLine(Math.Round(3.5));

The program output is:
2
4
Oops. Did you expect that? It appears that rounding in C# is different from Java. Indeed, let's take a look microsoft documentation:

"The behavior of this method follows IEEE Standard 754, section 4. This kind of rounding is sometimes called rounding to nearest, or banker's rounding. It minimizes rounding errors that result from consistently rounding a midpoint value in a single direction."

So the rounding is actually quite different. And to make things even more complicated let's try this Java code:
System.out.println(new DecimalFormat("0").format(2.5));
System.out.println(new DecimalFormat("0").format(3.5));

It will print out
2
4

So, even Java can round numbers differently. Just imagine if you have client-server application with client side written on .Net and server side written on Java. Can you imagine a long nights with debugger trying to find out why balance-sheet is wrong? I can.

Fortunately, in .Net it's quite easy to fix, since Round is overloaded Round(Decimal) / Round(Decimal, MidpointRounding) and one can explicetely set the type of rounding:
Console.WriteLine(Math.Round(2.5, MidpointRounding.AwayFromZero));
Console.WriteLine(Math.Round(3.5, MidpointRounding.AwayFromZero));

Unfortunately, I don't know an easy way to make Java work as C#. You have to either use BigDecimal with MathContext set to ROUND_HALF_EVEN or roll your own code.