views:

845

answers:

5
+5  Q: 

.Net Round Bug

The Math.Round function for .Net 3.5 SP1 appears to round 0.5 to zero, while it rounds 1.5 to 2.0. I have tested this with decimal numbers, and the following code:

decimal pos = 0.5m;
decimal neg = -0.5m;

Console.WriteLine("Pos: {0} Rnd: {1}", pos, Math.Round(pos));
Console.WriteLine("Neg: {0} Rnd: {1}", neg, Math.Round(neg));

Console.ReadKey();

This code outputs the following:

Pos: 0.5 Rnd: 0 Neg: -0.5 Rnd: 0

This seems like a glaring bug. Is there a known work around? I've tested this on a Core2 processor, and an i7, so it does not appear to be hardware. And Reflector just says that the decimal.round function ultimately calls a system call.

Let me know if anyone else see's this.

+25  A: 

That's called rounding to even, which is a valid rounding strategy for minimizing accrued errors in sums (MidpointRounding.ToEven). The theory is that, if you always round a 0.5 number in the same direction, the errors will accrue faster (round-to-even is supposed to minimize that).

Here's an extract from another question I answered that explains the different rounding strategies in .NET:

Follow these links for the MSDN descriptions of:

  • Math.Floor, which rounds down towards negative infinity.
  • Math.Ceiling, which rounds up towards positive infinity.
  • Math.Truncate, which rounds up or down towards zero.
  • Math.Round, which rounds to the nearest integer or specified number of decimal places. You can specify the behavior if it's exactly equidistant between two possibilities, such as rounding so that the final digit is even ("Round(2.5,MidpointRounding.ToEven)" becoming 2) or so that it's further away from zero ("Round(2.5,MidpointRounding.AwayFromZero)" becoming 3).

The following diagram and table may help:

-3        -2        -1         0         1         2
 +--|------+---------+----|----+--|------+----|--|-+
    a                     b       c           d  e

                       a=-2.7  b=-0.5  c=0.3  d=1.5  e=2.8
                       ======  ======  =====  =====  =====
Floor                    -3      -1      0      1      2
Ceiling                  -2       0      1      2      3
Truncate                 -2       0      0      1      2
Round(ToEven)            -3       0      0      2      3
Round(AwayFromZero)      -3      -1      0      2      3

Note that Round is a lot more powerful than it seems, simply because it can round to a specific number of decimal places. All the others round to zero decimals always. For example:

n = 3.145;
a = System.Math.Round (n, 2, MidpointRounding.ToEven);       // 3.14
b = System.Math.Round (n, 2, MidpointRounding.AwayFromZero); // 3.15

With the other functions, you have to use multiply/divide trickery to achieve the same effect:

c = System.Math.Truncate (n * 100) / 100;                    // 3.14
d = System.Math.Ceiling (n * 100) / 100;                     // 3.15
paxdiablo
Also known as Banker's Rounding
Pondidum
+10  A: 

From MSDN:

By default, Math.Round uses MidpointRounding.ToEven. Most people are not familiar with "rounding to even" as the alternative, "rounding away from zero" is more commonly taught in school. .NET defaults to "Rounding to even" as it is statistically superior because it doesn't share the tendency of "rounding away from zero" to round up slightly more often than it rounds down (assuming the numbers being rounded tend to be positive.)

http://msdn.microsoft.com/en-us/library/system.math.round.aspx

Cristian Donoso
+2  A: 

It's actually a feature. See the MSDN documentation.

This kind of rounding is known as banker's rounding.

As for a workaround, there is an overload that allows the caller to specify how to do the rounding.

Joe
A: 

This post has the answer you are looking for:

http://weblogs.asp.net/sfurman/archive/2003/03/07/3537.aspx

Basically this is what it says:

Return Value

The number nearest value with precision equal to digits. If value is halfway between two numbers, one of which is even and the other odd, then the even number is returned. If the precision of value is less than digits, then value is returned unchanged.

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. If digits is zero, this kind of rounding is sometimes called rounding toward zero.

Vaccano
+11  A: 

The nature of rounding

Consider the task of rounding a number that contains a fraction to, say, a whole number. The process of rounding in this circumstance is to determine which whole number best represents the number you are rounding.

In common, or 'arithmetic' rounding, it is clear that 2.1, 2.2, 2.3 and 2.4 round to 2.0; and 2.6, 2.7, 2.8 and 2.9 to 3.0.

That leaves 2.5, which is no nearer to 2.0 than it is to 3.0. It is up to you to choose between 2.0 and 3.0, either would be equally valid.

For minus numbers, -2.1, -2.2, -2.3 and -2.4, would become -2.0; and -2.6, 2.7, 2.8 and 2.9 would become -3.0 under arithmetic rounding.

For -2.5 a choice is needed between -2.0 and -3.0.

Other forms of rounding

'Rounding up' takes any number with decimal places and makes it the next 'whole' number. Thus not only do 2.5 and 2.6 round to 3.0, but so do 2.1 and 2.2.

Rounding up moves both positive and negative numbers away from zero. Eg. 2.5 to 3.0 and -2.5 to -3.0.

'Rounding down' truncates numbers by chopping off unwanted digits. This has the effect of moving numbers towards zero. Eg. 2.5 to 2.0 and -2.5 to -2.0

In "banker's rounding" - in its most common form - the .5 to be rounded is rounded either up or down so that the result of the rounding is always an even number. Thus 2.5 rounds to 2.0, 3.5 to 4.0, 4.5 to 4.0, 5.5 to 6.0, and so on.

'Alternate rounding' alternates the process for any .5 between rounding down and rounding up.

'Random rounding' rounds a .5 up or down on an entirely random basis.

Symmetry and asymmetry

A rounding function is said to be 'symmetric' if it either rounds all numbers away from zero or rounds all numbers towards zero.

A function is 'asymmetric' if rounds positive numbers towards zero and negative numbers away from zero.. Eg. 2.5 to 2.0; and -2.5 to -3.0.

Also asymmetric is a function that rounds positive numbers away from zero and negative numbers towards zero. Eg. 2.5 to 3.0; and -2.5 to -2.0.

Most of time people think of symmetric rounding, where -2.5 will be rounded towards -3.0 and 3.5 will be rounded towards 4.0. (in C# Round(AwayFromZero))

Patrick Peters