ChatGPT解决这个技术问题 Extra ChatGPT

Why does Math.Round(2.5) return 2 instead of 3?

In C#, the result of Math.Round(2.5) is 2.

It is supposed to be 3, isn't it? Why is it 2 instead in C#?

It's actually a feature. See <a href="msdn.microsoft.com/en-us/library/… MSDN documentation</a>. This kind of rounding is known as banker's rounding. As for a workaround, there is <a href="msdn.microsoft.com/en-us/library/… overload</a> that allows the caller to specify how to do the rounding.
Apparently the round method, when asked to round a number exactly between two integers, returns the even integer. So, Math.Round(3.5) returns 4. See this article
Math.Round(2.5, 0, MidpointRounding.AwayFromZero);
SQL Server rounds that way; interesting test results when there is a C# unit test ti validate rounding done in T-SQL.
@amed that's not a bug. It's the way binary floating points work. 1.005 can't be represented exactly in double. It's probably 1.00499.... If you use Decimal this problem will disappear. The existence of the Math.Round overload that takes a number of decimal digits on double is a dubious design choice IMO, since it will rarely work in a meaningful way.

J
Jon Skeet

Firstly, this wouldn't be a C# bug anyway - it would be a .NET bug. C# is the language - it doesn't decide how Math.Round is implemented.

And secondly, no - if you read the docs, you'll see that the default rounding is "round to even" (banker's rounding):

Return Value Type: System.Double The integer nearest a. If the fractional component of a is halfway between two integers, one of which is even and the other odd, then the even number is returned. Note that this method returns a Double instead of an integral type. Remarks 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.

You can specify how Math.Round should round mid-points using an overload which takes a MidpointRounding value. There's one overload with a MidpointRounding corresponding to each of the overloads which doesn't have one:

Round(Decimal) / Round(Decimal, MidpointRounding)

Round(Double) / Round(Double, MidpointRounding)

Round(Decimal, Int32) / Round(Decimal, Int32, MidpointRounding)

Round(Double, Int32) / Round(Double, Int32, MidpointRounding)

Whether this default was well chosen or not is a different matter. (MidpointRounding was only introduced in .NET 2.0. Before then I'm not sure there was any easy way of implementing the desired behaviour without doing it yourself.) In particular, history has shown that it's not the expected behaviour - and in most cases that's a cardinal sin in API design. I can see why Banker's Rounding is useful... but it's still a surprise to many.

You may be interested to take a look at the nearest Java equivalent enum (RoundingMode) which offers even more options. (It doesn't just deal with midpoints.)


i dont know if this is a bug, i think it was by design since .5 is as close to the nearest lowest integer as it is to the nearest highest integer.
I remember this behavior in VB before .NET got applied.
Indeed, IEEE Standard 754, section 4 as the documentation states.
I got burned by this a while ago and thought it was sheer lunacy too. Fortunately they added a way to specify the rounding that all of us learned in grade-school; MidPointRounding.
+1 for "it's not the expected behaviour [...] that's a cardinal sin in API design"
p
paxdiablo

That's called rounding to even (or banker's rounding), 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) (a).

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         3
 +--|------+---------+----|----+--|------+----|----+-------|-+
    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

(a) Of course, that theory depends on the fact that your data has an fairly even spread of values across the even halves (0.5, 2.5, 4.5, ...) and odd halves (1.5, 3.5, ...).

If all the "half-values" are evens (for example), the errors will accumulate just as fast as if you always rounded up.


Also known as Banker's Rounding
Good explanation! I wanted to see for myself how the error accumulates and I wrote a script that shows that values rounded using banker's rounding, in the long run, have their sums and averages much closer to these of original values. github.com/AmadeusW/RoundingDemo (pictures of plots available)
Just a short time after: shouldn't e tick (= 2.8) be further right than 2 tick?
A simple way to remember, and assuming the tenths place is 5: - ones place and tenth place are all odd = round up - ones place and tenth place are mixed = round down * Zero is not odd * Reversed for negative numbers
@ArkhamAngel, that actually seems harder to remember than just "make the last digit even" :-)
c
casperOne

From MSDN, Math.Round(double a) returns:

The integer nearest a. If the fractional component of a is halfway between two integers, one of which is even and the other odd, then the even number is returned.

... and so 2.5, being halfway between 2 and 3, is rounded down to the even number (2). this is called Banker's Rounding (or round-to-even), and is a commonly-used rounding standard.

Same MSDN article:

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.

You can specify a different rounding behavior by calling the overloads of Math.Round that take a MidpointRounding mode.


D
Dirk Vollmar

You should check MSDN for Math.Round:

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.

You can specify the behavior of Math.Round using an overload:

Math.Round(2.5, 0, MidpointRounding.AwayFromZero); // gives 3

Math.Round(2.5, 0, MidpointRounding.ToEven); // gives 2

P
Patrick Peters

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))


C
Community

The default MidpointRounding.ToEven, or Bankers' rounding (2.5 become 2, 4.5 becomes 4 and so on) has stung me before with writing reports for accounting, so I'll write a few words of what I found out, previously and from looking into it for this post.

Who are these bankers that are rounding down on even numbers (British bankers perhaps!)?

From wikipedia

The origin of the term bankers' rounding remains more obscure. If this rounding method was ever a standard in banking, the evidence has proved extremely difficult to find. To the contrary, section 2 of the European Commission report The Introduction of the Euro and the Rounding of Currency Amounts suggests that there had previously been no standard approach to rounding in banking; and it specifies that "half-way" amounts should be rounded up.

It seems a very strange way of rounding particularly for banking, unless of course banks use to receive lots of deposits of even amounts. Deposit £2.4m, but we'll call it £2m sir.

The IEEE Standard 754 dates back to 1985 and gives both ways of rounding, but with banker's as the recommended by the standard. This wikipedia article has a long list of how languages implement rounding (correct me if any of the below are wrong) and most don't use Bankers' but the rounding you're taught at school:

C/C++ round() from math.h rounds away from zero (not banker's rounding)

Java Math.Round rounds away from zero (it floors the result, adds 0.5, casts to an integer). There's an alternative in BigDecimal

Perl uses a similar way to C

Javascript is the same as Java's Math.Round.


Thanks for the information. I never realised this. Your example about the millions ridicules it a bit, but even if you round on cents, having to pay interest on 10 million bank accounts will cost the bank a lot if all half cents are rounded up, or will cost the clients a lot if all half cents are rounded down. So i can imagine this is the agreed standard. Not sure if this is really used by bankers though. Most customers won't notice rounding down, while bringing in a lot of money, but I can imagine this is obliged by laws if you live in a country with customer-friendly laws
C
Cristian Donoso

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


J
JBrooks

Since Silverlight doesn't support the MidpointRounding option you have to write your own. Something like:

public double RoundCorrect(double d, int decimals)
{
    double multiplier = Math.Pow(10, decimals);

    if (d < 0)
        multiplier *= -1;

    return Math.Floor((d * multiplier) + 0.5) / multiplier;

}

For the examples including how to use this as an extension see the post: .NET and Silverlight Rounding


S
ShortFuse

I had this problem where my SQL server rounds up 0.5 to 1 while my C# application didn't. So you would see two different results.

Here's an implementation with int/long. This is how Java rounds.

int roundedNumber = (int)Math.Floor(d + 0.5);

It's probably the most efficient method you could think of as well.

If you want to keep it a double and use decimal precision , then it's really just a matter of using exponents of 10 based on how many decimal places.

public double getRounding(double number, int decimalPoints)
{
    double decimalPowerOfTen = Math.Pow(10, decimalPoints);
    return Math.Floor(number * decimalPowerOfTen + 0.5)/ decimalPowerOfTen;
}

You can input a negative decimal for decimal points and it's word fine as well.

getRounding(239, -2) = 200

j
jascur2

Silverlight doesn't support the MidpointRounding option. Here's an extension method for Silverlight that adds the MidpointRounding enum:

public enum MidpointRounding
{
    ToEven,
    AwayFromZero
}

public static class DecimalExtensions
{
    public static decimal Round(this decimal d, MidpointRounding mode)
    {
        return d.Round(0, mode);
    }

    /// <summary>
    /// Rounds using arithmetic (5 rounds up) symmetrical (up is away from zero) rounding
    /// </summary>
    /// <param name="d">A Decimal number to be rounded.</param>
    /// <param name="decimals">The number of significant fractional digits (precision) in the return value.</param>
    /// <returns>The number nearest d with precision equal to decimals. If d is halfway between two numbers, then the nearest whole number away from zero is returned.</returns>
    public static decimal Round(this decimal d, int decimals, MidpointRounding mode)
    {
        if ( mode == MidpointRounding.ToEven )
        {
            return decimal.Round(d, decimals);
        }
        else
        {
            decimal factor = Convert.ToDecimal(Math.Pow(10, decimals));
            int sign = Math.Sign(d);
            return Decimal.Truncate(d * factor + 0.5m * sign) / factor;
        }
    }
}

Source: http://anderly.com/2009/08/08/silverlight-midpoint-rounding-solution/


N
Nalan Madheswaran

Simple way is:

Math.Ceiling(decimal.Parse(yourNumber + ""));

You know about the concept of casting, right?
L
Lance U. Matthews

Rounding numbers with .NET has the answer you are looking for.

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.


a
anandd360

using a custom rounding

public int Round(double value)
{
    double decimalpoints = Math.Abs(value - Math.Floor(value));
    if (decimalpoints > 0.5)
        return (int)Math.Round(value);
    else
        return (int)Math.Floor(value);
}

>.5 produces the same behaviour as Math.Round. The question is whas happens when the decimal part is exactly 0.5. Math.Round allows you to specify the kind of rounding algorithm you want
K
Kalu Singh Rao

Here's the way i had to work it around :

Public Function Round(number As Double, dec As Integer) As Double
    Dim decimalPowerOfTen = Math.Pow(10, dec)
    If CInt(number * decimalPowerOfTen) = Math.Round(number * decimalPowerOfTen, 2) Then
        Return Math.Round(number, 2, MidpointRounding.AwayFromZero)
    Else
        Return CInt(number * decimalPowerOfTen + 0.5) / 100
    End If
End Function

Trying with 1.905 with 2 decimals will give 1.91 as expected but Math.Round(1.905,2,MidpointRounding.AwayFromZero) gives 1.90! Math.Round method is absolutely inconsistent and unusable for most of the basics problems programmers may encounter. I have to check if (int) 1.905 * decimalPowerOfTen = Math.Round(number * decimalPowerOfTen, 2) cause i don not want to round up what should be round down.


Math.Round(1.905,2,MidpointRounding.AwayFromZero) returns 1.91
J
James Montagne

This is ugly as all hell, but always produces correct arithmetic rounding.

public double ArithRound(double number,int places){

  string numberFormat = "###.";

  numberFormat = numberFormat.PadRight(numberFormat.Length + places, '#');

  return double.Parse(number.ToString(numberFormat));

}

So does calling Math.Round and specifying how you want it to round.