Perché Math.Round (2.5) restituisce 2 anziché 3?


416

In C #, il risultato di Math.Round(2.5)è 2.

Dovrebbe essere 3, no? Perché invece è 2 in C #?


5
In realtà è una caratteristica. Vedere <a href=" msdn.microsoft.com/en-us/library/… Documentazione MSDN</a> . Questo tipo di arrotondamento è noto come arrotondamento del banchiere. Per quanto riguarda la soluzione alternativa, esiste <a href = " msdn. microsoft.com/en-us/library/… overload </a> che consente al chiamante di specificare come eseguire l'arrotondamento.
Joe,

1
Apparentemente il metodo round, quando viene chiesto di arrotondare un numero esattamente tra due numeri interi, restituisce il numero intero pari. Quindi, Math.Round (3.5) restituisce 4. Vedi questo articolo
Matthew Jones,

20
Math.Round(2.5, 0, MidpointRounding.AwayFromZero);
Robert Durgin,

SQL Server arrotonda in quel modo; risultati di test interessanti quando c'è un test unitario C # per convalidare l'arrotondamento fatto in T-SQL.
idstam,

7
@amed non è un bug. È il modo in cui funzionano i punti mobili binari. 1.005non può essere rappresentato esattamente in doppio. Probabilmente lo è 1.00499.... Se si utilizza Decimalquesto problema scomparirà. L'esistenza del sovraccarico Math.Round che richiede un numero di cifre decimali sul doppio è una scelta di progettazione dubbia IMO, poiché raramente funzionerà in modo significativo.
CodesInChaos

Risposte:


561

In primo luogo, questo non sarebbe comunque un bug C # - sarebbe un bug .NET. C # è il linguaggio - non decide come Math.Roundviene implementato.

In secondo luogo, no: se leggi i documenti , vedrai che l'arrotondamento predefinito è "arrotondato a pari" (arrotondamento del banchiere):

Valore restituito
Tipo: System.Double
L'intero più vicino a. Se la componente frazionaria di a è a metà strada tra due numeri interi, uno dei quali è pari e l'altro dispari, viene restituito il numero pari. Si noti che questo metodo restituisce un tipo Doubleinvece di un integrale.

Note
Il comportamento di questo metodo segue lo standard IEEE 754, sezione 4. Questo tipo di arrotondamento viene talvolta chiamato arrotondamento al più vicino o arrotondamento del banco. Riduce al minimo gli errori di arrotondamento risultanti dall'arrotondamento costante di un valore del punto medio in una sola direzione.

È possibile specificare come Math.Roundarrotondare i punti medi usando un sovraccarico che assume un MidpointRoundingvalore. C'è un sovraccarico con un MidpointRoundingcorrispondente a ciascuno dei sovraccarichi che non ne ha uno:

Se questa impostazione predefinita è stata scelta correttamente o no è una questione diversa. (è MidpointRoundingstato introdotto solo in .NET 2.0. Prima di allora non sono sicuro che esistesse un modo semplice per implementare il comportamento desiderato senza farlo da soli.) In particolare, la storia ha dimostrato che non è il comportamento previsto - e nella maggior parte dei casi è un peccato cardinale nella progettazione delle API. Vedo perché l'arrotondamento del banco è utile ... ma è ancora una sorpresa per molti.

Potresti essere interessato a dare un'occhiata all'enum equivalente ( RoundingMode) di Java più vicino che offre ancora più opzioni. (Non si occupa solo di punti medi.)


4
non so se si tratta di un bug, penso che sia stato progettato dal momento che .5 è il più vicino all'intero più basso più vicino come lo è all'intero più alto più vicino.
Stan R.

3
Ricordo questo comportamento in VB prima dell'applicazione di .NET.
John Fiala,

7
In effetti, lo standard IEEE 754, sezione 4 come afferma la documentazione.
Jon Skeet,

2
Ne sono stato bruciato qualche tempo fa e ho pensato che fosse pura follia. Fortunatamente hanno aggiunto un modo per specificare l'arrotondamento che tutti noi abbiamo imparato alle elementari; MidPointRounding.
Shea,

26
+1 per "non è il comportamento previsto [...] che è un peccato cardinale nella progettazione dell'API"
BlueRaja - Danny Pflughoeft

215

Questo si chiama arrotondamento a pari (o arrotondamento del banchiere), che è una strategia di arrotondamento valida per ridurre al minimo gli errori accumulati nelle somme (MidpointRounding.ToEven). La teoria è che, se si arrotonda sempre un numero 0,5 nella stessa direzione, gli errori si accumuleranno più velocemente (si suppone che il round-to-even minimizzi quello) (a) .

Segui questi collegamenti per le descrizioni MSDN di:

  • Math.Floor, che si arrotonda verso l'infinito negativo.
  • Math.Ceiling, che arrotonda verso l'infinito positivo.
  • Math.Truncate, che arrotonda per eccesso o per difetto verso zero.
  • Math.Round, che viene arrotondato al numero intero più vicino o al numero specificato di posizioni decimali. È possibile specificare il comportamento se è esattamente equidistante tra due possibilità, come arrotondare in modo che la cifra finale sia pari (" Round(2.5,MidpointRounding.ToEven)" diventando 2) o in modo che sia più lontana da zero (" Round(2.5,MidpointRounding.AwayFromZero)" diventando 3).

Il diagramma e la tabella seguenti possono essere d'aiuto:

-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

Nota che Roundè molto più potente di quanto sembri, semplicemente perché può arrotondare a un numero specifico di cifre decimali. Tutti gli altri arrotondano sempre a zero decimali sempre. Per esempio:

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

Con le altre funzioni, devi usare l'inganno moltiplicare / dividere per ottenere lo stesso effetto:

c = System.Math.Truncate (n * 100) / 100;                    // 3.14
d = System.Math.Ceiling (n * 100) / 100;                     // 3.15

(a) Naturalmente, questa teoria dipende dal fatto che i tuoi dati hanno una diffusione abbastanza uniforme dei valori tra le metà pari (0,5, 2,5, 4,5, ...) e le metà dispari (1,5, 3,5, ...).

Se tutti i "mezzi valori" sono pari (ad esempio), gli errori si accumuleranno altrettanto velocemente come se si fosse sempre arrotondati per eccesso.


3
Conosciuto anche come Banker's
Rounding

Buona spiegazione Volevo vedere da solo come si accumula l'errore e ho scritto una sceneggiatura che mostra che i valori arrotondati usando l'arrotondamento del banchiere, a lungo termine, hanno le loro somme e le medie molto più vicine a quelle dei valori originali. github.com/AmadeusW/RoundingDemo (foto delle trame disponibili)
Amadeusz Wieczorek

Poco dopo: il esegno di spunta (= 2.8) non dovrebbe essere più giusto del 2segno di spunta?
superjos,

Un modo semplice da ricordare, e supponendo che il decimo posto sia il 5: - il primo e il decimo posto sono tutti dispari = arrotondati per eccesso - quelli per posto e decimo sono mescolati = arrotondati per difetto * Zero non è dispari * Invertito per i numeri negativi
Arkham Angel

@ArkhamAngel, che in realtà sembra più difficile da ricordare di un semplice "pareggio dell'ultima cifra" :-)
paxdiablo

42

Da MSDN, Math.Round (double a) restituisce:

L'intero più vicino a. Se la componente frazionaria di a è a metà strada tra due numeri interi, uno dei quali è pari e l'altro dispari, viene restituito il numero pari.

... e quindi 2.5, essendo a metà strada tra 2 e 3, viene arrotondato per difetto al numero pari (2). questo è chiamato arrotondamento del banchiere (o arrotondato al pareggio) ed è uno standard di arrotondamento comunemente usato.

Stesso articolo MSDN:

Il comportamento di questo metodo segue lo standard IEEE 754, sezione 4. Questo tipo di arrotondamento viene talvolta chiamato arrotondamento al più vicino o arrotondamento del banco. Riduce al minimo gli errori di arrotondamento risultanti dall'arrotondamento costante di un valore del punto medio in una sola direzione.

È possibile specificare un diverso comportamento di arrotondamento chiamando i sovraccarichi di Math.Round che accettano una MidpointRoundingmodalità.


37

È necessario controllare MSDN per Math.Round:

Il comportamento di questo metodo segue lo standard IEEE 754, sezione 4. Questo tipo di arrotondamento viene talvolta chiamato arrotondamento al più vicino o arrotondamento del banco.

È possibile specificare il comportamento Math.Rounddell'utilizzo di un sovraccarico:

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

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

31

La natura dell'arrotondamento

Considera il compito di arrotondare un numero che contiene una frazione, per esempio, un numero intero. Il processo di arrotondamento in questa circostanza è determinare quale numero intero rappresenti meglio il numero da arrotondare.

Nell'arrotondamento comune o "aritmetico", è chiaro che 2.1, 2.2, 2.3 e 2.4 sono arrotondati a 2.0; e 2.6, 2.7, 2.8 e 2.9 a 3.0.

Questo lascia 2.5, che non è più vicino a 2.0 che a 3.0. Sta a te scegliere tra 2.0 e 3.0, entrambi sarebbero ugualmente validi.

Per i numeri meno, -2,1, -2,2, -2,3 e -2,4, diventerebbero -2,0; e -2,6, 2,7, 2,8 e 2,9 diventerebbero -3,0 in arrotondamenti aritmetici.

Per -2,5 è necessaria una scelta tra -2,0 e -3,0.

Altre forme di arrotondamento

L'arrotondamento prende qualsiasi numero con le cifre decimali e lo rende il numero "intero" successivo. Quindi non solo 2.5 e 2.6 arrotondati a 3.0, ma anche 2.1 e 2.2.

L'arrotondamento allontana i numeri positivi e negativi da zero. Per esempio. Da 2,5 a 3,0 e da -2,5 a -3,0.

L'arrotondamento tronca i numeri tagliando le cifre indesiderate. Ciò ha l'effetto di spostare i numeri verso lo zero. Per esempio. Da 2,5 a 2,0 e da -2,5 a -2,0

Nel "arrotondamento del banco" - nella sua forma più comune - lo 0,5 da arrotondare viene arrotondato verso l'alto o verso il basso in modo che il risultato dell'arrotondamento sia sempre un numero pari. Quindi 2,5 giri a 2,0, da 3,5 a 4,0, da 4,5 a 4,0, da 5,5 a 6,0 e così via.

L '"arrotondamento alternativo" alterna il processo per qualsiasi 0,5 tra arrotondamento per difetto e arrotondamento per eccesso.

L''arrotondamento casuale 'arrotonda uno .5 su o giù su una base completamente casuale.

Simmetria e asimmetria

Si dice che una funzione di arrotondamento è "simmetrica" ​​se arrotonda tutti i numeri da zero o arrotonda tutti i numeri verso zero.

Una funzione è "asimmetrica" ​​se arrotonda i numeri positivi verso zero e i numeri negativi da zero. Es. Da 2,5 a 2,0; e da -2,5 a -3,0.

Anche l'asimmetria è una funzione che arrotonda i numeri positivi a zero e i numeri negativi a zero. Per esempio. Da 2,5 a 3,0; e da -2,5 a -2,0.

Il più delle volte le persone pensano all'arrotondamento simmetrico, dove -2,5 sarà arrotondato a -3,0 e 3,5 sarà arrotondato a 4,0. (in C #Round(AwayFromZero))


28

Predefinito MidpointRounding.ToEveno arrotondamento dei banchieri ( 2.5 diventano 2, 4.5 diventano 4 e così via ) mi ha colpito prima con la scrittura di report per la contabilità, quindi scriverò alcune parole di ciò che ho scoperto, in precedenza e dall'analisi questo post.

Chi sono questi banchieri che arrotondano per eccesso i numeri pari (forse i banchieri britannici!)?

Da Wikipedia

L'origine del termine arrotondamento dei banchieri rimane più oscura. Se questo metodo di arrotondamento è mai stato uno standard nel settore bancario, le prove si sono rivelate estremamente difficili da trovare. Al contrario, la sezione 2 della relazione della Commissione europea L'introduzione dell'euro e l'arrotondamento degli importi in valuta suggerisce che in precedenza non esisteva un approccio standard all'arrotondamento nel settore bancario; e specifica che gli importi "a metà strada" dovrebbero essere arrotondati per eccesso.

Sembra un modo molto strano di arrotondare in particolare per il settore bancario, a meno che, naturalmente, le banche non usino ricevere molti depositi di importi pari. Cauzione £ 2,4 milioni, ma lo chiameremo £ 2 milioni signore.

Lo standard IEEE 754 risale al 1985 e offre entrambi i modi di arrotondamento, ma con il banchiere come raccomandato dallo standard. Questo articolo di Wikipedia ha un lungo elenco di come le lingue implementano l'arrotondamento (correggimi se uno dei seguenti è sbagliato) e la maggior parte non usa i banchieri ma l'arrotondamento che ti viene insegnato a scuola:

  • Round C / C ++ () da round matematici a partire da zero (non arrotondamento del banchiere)
  • Java Math.Round arrotonda a zero ( rafforza il risultato, aggiunge 0,5, lancia a un numero intero). C'è un'alternativa in BigDecimal
  • Perl usa un modo simile a C
  • Javascript è uguale a Math.Round di Java.

Grazie per l'informazione. Non l'ho mai capito. Il tuo esempio sui milioni di ridicoli è un po ', ma anche se arrotondi i centesimi, dover pagare gli interessi su 10 milioni di conti bancari costerà molto alla banca se tutti i centesimi sono arrotondati, o costerà molto ai clienti se tutti la metà è arrotondata per difetto. Quindi posso immaginare che questo sia lo standard concordato. Non sono sicuro se questo è davvero utilizzato dai banchieri però. La maggior parte dei clienti non noterà l'arrotondamento, mentre porta un sacco di soldi, ma posso immaginare che questo sia obbligato dalle leggi se vivi in ​​un paese con leggi a misura di cliente
Harald Coppoolse

15

Da MSDN:

Per impostazione predefinita, Math.Round utilizza MidpointRounding.ToEven. La maggior parte delle persone non ha familiarità con il "arrotondamento a pari" come alternativa, "arrotondamento da zero" è più comunemente insegnato a scuola. Il valore predefinito di .NET è "Arrotondamento a pari" poiché è statisticamente superiore perché non condivide la tendenza di "arrotondamento da zero" a arrotondare leggermente più spesso di quanto non arrotonda (supponendo che i numeri arrotondati tendano ad essere positivi. )

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


3

Poiché Silverlight non supporta l'opzione MidpointRounding, devi scrivere la tua. Qualcosa di simile a:

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;

}

Per gli esempi che includono come utilizzare questo come estensione, vedere il post: .NET e Silverlight Rounding


3

Ho avuto questo problema in cui il mio server SQL arrotonda da 0,5 a 1, mentre la mia applicazione C # no. Quindi vedresti due risultati diversi.

Ecco un'implementazione con int / long. Ecco come si arrotonda Java.

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

È probabilmente anche il metodo più efficiente a cui potresti pensare.

Se vuoi mantenerlo doppio e usare la precisione decimale, allora è davvero solo una questione di usare esponenti di 10 in base a quanti decimali.

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

Puoi inserire un decimale negativo per i punti decimali e anche la parola va bene.

getRounding(239, -2) = 200


0

Questo post ha la risposta che stai cercando:

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

Fondamentalmente questo è ciò che dice:

Valore di ritorno

Il valore numerico più vicino con precisione pari a cifre. Se il valore è a metà strada tra due numeri, uno dei quali è pari e l'altro dispari, viene restituito il numero pari. Se la precisione del valore è inferiore alle cifre, il valore viene restituito invariato.

Il comportamento di questo metodo segue lo standard IEEE 754, sezione 4. Questo tipo di arrotondamento viene talvolta chiamato arrotondamento al più vicino o arrotondamento del banco. Se le cifre sono zero, questo tipo di arrotondamento viene talvolta chiamato arrotondamento verso zero.


0

Silverlight non supporta l'opzione MidpointRounding. Ecco un metodo di estensione per Silverlight che aggiunge l'enumerazione MidpointRounding:

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;
        }
    }
}

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


-1

usando un arrotondamento personalizzato

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

>.5produce lo stesso comportamento di Math.Round. La domanda è: cosa succede quando la parte decimale è esattamente 0.5. Math.Round ti consente di specificare il tipo di algoritmo di arrotondamento desiderato
Panagiotis Kanavos,

-2

Questo è brutto come l'inferno, ma produce sempre un corretto arrotondamento aritmetico.

public double ArithRound(double number,int places){

  string numberFormat = "###.";

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

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

}

5
Lo stesso Math.Roundvale per chiamare e specificare come si desidera arrotondare.
configuratore

-2

Ecco come ho dovuto aggirarlo:

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

Provare con 1.905 con 2 decimali darà 1,91 come previsto ma Math.Round(1.905,2,MidpointRounding.AwayFromZero)dà 1,90! Il metodo Math.Round è assolutamente incoerente e inutilizzabile per la maggior parte dei problemi di base che i programmatori possono incontrare. Devo verificare se (int) 1.905 * decimalPowerOfTen = Math.Round(number * decimalPowerOfTen, 2)non voglio arrotondare ciò che dovrebbe essere arrotondato.


Math.Round(1.905,2,MidpointRounding.AwayFromZero) ritorna 1.91
Panagiotis Kanavos il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.