Math.Round (2.5) neden 3 yerine 2 döndürüyor?


415

C # 'da sonucu Math.Round(2.5)2 olur.

3 olması gerekiyordu, değil mi? Neden C # yerine 2?


5
Aslında bir özellik. <a href=" msdn.microsoft.com/tr-tr/library/… MSDN dokümantasyon</a> Bu tür bir yuvarlama bankacının yuvarlanması olarak bilinir . Arayanın yuvarlamayı nasıl yapacağını belirtmesini sağlayan microsoft.com/en-us/library/… aşırı yükleme </a>.
Joe

1
Görünüşe göre yuvarlak yöntem, bir sayıyı tam olarak iki tamsayı arasında yuvarlaması istendiğinde, çift tamsayıyı döndürür. Yani Math.Round (3.5) 4 döndürür. Bu makaleye bakın
Matthew Jones

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

SQL Server bu şekilde yuvarlar; T-SQL'de yapılan bir C # birimi test ti validate yuvarlama olduğunda ilginç test sonuçları.
idstam

7
@amed bu bir hata değil. İkili kayan noktaların çalışma şekli budur. 1.005tam olarak çift olarak temsil edilemez. Muhtemelen 1.00499.... Kullanırsanız Decimalbu sorun ortadan kalkacaktır. Çift üzerinde bir kaç ondalık basamak alan Math.Round aşırı yükünün varlığı, nadiren anlamlı bir şekilde çalışacağı için şüpheli bir tasarım seçim IMO'sudur.
CodesInChaos

Yanıtlar:


560

Birincisi, bu zaten bir C # hatası olmaz - bir .NET hatası olurdu. C # dildir - nasıl Math.Rounduygulandığına karar vermez .

İkincisi, hayır - dokümanları okursanız , varsayılan yuvarlamanın "düz - yuvarla" (bankacının yuvarlanması) olduğunu görürsünüz:

Dönüş Değeri
Tür: System.Double
En yakın tam sayı a. A'nın kesirli bileşeni, biri çift diğeri tek olmak üzere iki tamsayı arasındaysa, çift sayı döndürülür. Bu yöntemin Doublebir integral türü yerine bir döndürdüğünü unutmayın .

Açıklamalar
Bu yöntemin davranışı IEEE Standard 754, bölüm 4'ü izler. Bu tür yuvarlamaya bazen en yakın yuvarlama veya bankacının yuvarlanması denir. Bir orta nokta değerinin sürekli olarak tek bir yönde yuvarlanmasından kaynaklanan yuvarlama hatalarını en aza indirir.

Değer alan bir aşırı yükMath.Round kullanarak orta noktaların nasıl yuvarlanması gerektiğini belirleyebilirsiniz . Her bir aşırı yüke karşılık gelen bir aşırı yük var:MidpointRoundingMidpointRounding

Bu varsayılanın iyi seçilmiş olup olmadığı farklı bir konudur. ( MidpointRoundingyalnızca .NET 2.0'da tanıtıldı. O zamandan önce, istenen davranışı kendiniz yapmadan uygulamanın kolay bir yolu olmadığından emin değilim.) Özellikle, tarih, beklenen davranış olmadığını gösterdi - ve çoğu durumda bu API tasarımında önemli bir günah. Görebildiğim neden Banker Yuvarlama yararlıdır ... ama birçok hala bir sürpriz.

Daha RoundingModeda fazla seçenek sunan en yakın Java eşdeğeri enum'a ( ) bir göz atmak isteyebilirsiniz . (Sadece orta noktalarla ilgilenmez.)


4
Bu bir hata olup olmadığını bilmiyorum, çünkü tasarım olarak olduğunu düşünüyorum. 5 en yakın en yüksek tamsayı olduğu gibi en yakın en düşük tam sayıya yakın.
Stan R.

3
.NET uygulanmadan önce VB bu davranışı hatırlıyorum.
John Fiala

7
Aslında, IEEE Standard 754, bölüm 4, dokümantasyonda belirtildiği gibi.
Jon Skeet

2
Bir süre önce bu işten yanmıştım ve bunun da saf bir zevk olduğunu düşündüm. Neyse ki, hepimizin ilkokulda öğrendiği yuvarlamayı belirtmek için bir yol eklediler; MidPointRounding.
Shea

26
+1 "beklenen davranış değil [...] bu API tasarımında önemli bir günahtır"
BlueRaja - Danny Pflughoeft

215

Buna, toplamlara tahakkuk eden hataları en aza indirmek için geçerli bir yuvarlama stratejisi olan çift (ya da bankacının yuvarlaması) yuvarlama denir (MidpointRounding.ToEven). Teori, her zaman aynı yönde 0,5 sayıyı yuvarlarsanız, hataların daha hızlı tahakkuk edeceği (hatta en aza indirgemesi gerekiyor) (a) .

Aşağıdaki MSDN açıklamaları için şu bağlantıları izleyin:

  • Math.Floornegatif sonsuzluğa doğru yuvarlanır.
  • Math.Ceilingpozitif sonsuzluğa doğru yuvarlar.
  • Math.Truncate, sıfıra doğru yukarı veya aşağı yuvarlar.
  • Math.Round, en yakın tam sayıya veya belirtilen ondalık basamağa yuvarlar. Son basamak eşit olacak şekilde (" Round(2.5,MidpointRounding.ToEven)" 2 oluyor) ya da sıfırdan daha uzakta olacak şekilde (" Round(2.5,MidpointRounding.AwayFromZero)" 3 oluyor) yuvarlama gibi iki olasılık arasında tam olarak eşitse davranışı belirleyebilirsiniz .

Aşağıdaki şema ve tablo yardımcı olabilir:

-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

RoundBunun göründüğünden çok daha güçlü olduğunu unutmayın, çünkü belirli bir ondalık basamağa yuvarlanabilir. Diğerleri her zaman sıfır ondalık sayıya yuvarlanır. Örneğin:

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

Diğer işlevlerle, aynı etkiyi elde etmek için çarpma / bölme hilelerini kullanmanız gerekir:

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

(a) Tabii ki, bu teori verilerinizin çift yarılar (0.5, 2.5, 4.5, ...) ve tek yarılar (1.5, 3.5, ...) arasında oldukça eşit bir değer yayılımına sahip olmasına bağlıdır.

Eğer bütün "yarı değerler" (örneğin) eşitler olan her zaman yuvarlanır sanki, hatalar sadece hızlı birikecektir.


3
Banker
Rounding

İyi açıklama! Kendim için hatanın nasıl biriktiğini görmek istedim ve bankacının yuvarlanmasıyla yuvarlanan değerlerin uzun vadede toplamlarının ve ortalamalarının bu orijinal değerlere çok daha yakın olduğunu gösteren bir senaryo yazdım. github.com/AmadeusW/RoundingDemo (çizimler mevcut)
Amadeusz Wieczorek

Sadece kısa bir süre sonra: eKene (= 2.8) 2kene değil mi?
superjos

Hatırlamanın basit bir yolu ve onuncu yer olduğunu varsayalım 5: - yer ve onuncu yer hepsi garip = yuvarla - yer ve onuncu yer karıştırılır = yuvarlatılmış * Sıfır garip değil * Negatif sayılar için ters çevrilmiş
Arkham Angel

@ArkhamAngel, aslında "son rakamı eşit yap" dan daha hatırlamak zor görünüyor :-)
paxdiablo

42

Gönderen MSDN, Math.Round (çift a) döndürür:

En yakın tam sayı a. A'nın kesirli bileşeni, biri çift diğeri tek olmak üzere iki tamsayı arasındaysa, çift sayı döndürülür.

... ve böylece 2.5, 2 ve 3'ün yarısı olmak üzere, çift sayıya (2) yuvarlanır. buna Banker Yuvarlama (ya da çift yönlü) denir ve yaygın olarak kullanılan bir yuvarlama standardıdır.

Aynı MSDN makalesi:

Bu yöntemin davranışı IEEE Standard 754, bölüm 4'ü izler. Bu tür yuvarlamaya bazen en yakın yuvarlama veya bankacının yuvarlanması denir. Bir orta nokta değerinin sürekli olarak tek bir yönde yuvarlanmasından kaynaklanan yuvarlama hatalarını en aza indirir.

Bir MidpointRoundingmod alan Math.Round'un aşırı yüklenmelerini çağırarak farklı bir yuvarlama davranışı belirleyebilirsiniz.


37

MSDN için aşağıdakileri kontrol etmelisiniz Math.Round:

Bu yöntemin davranışı IEEE Standard 754, bölüm 4'ü izler. Bu tür yuvarlamaya bazen en yakın yuvarlama veya bankacının yuvarlanması denir.

Math.RoundBir aşırı yük kullanma davranışını belirleyebilirsiniz :

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

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

31

Yuvarlamanın doğası

Kesir içeren bir sayıyı, örneğin bir tam sayıya yuvarlama görevini düşünün. Bu durumda yuvarlama işlemi, yuvarladığınız sayıyı hangi tam sayının en iyi temsil ettiğini belirlemektir.

Ortak veya 'aritmetik' yuvarlamada 2.1, 2.2, 2.3 ve 2.4'ün 2.0'a yuvarlandığı açıktır; ve 2.6, 2.7, 2.8 ve 2.9 ila 3.0.

Bu, 2.5'e, 3.0'a 3.0'dan daha yakın değil. Her ikisi de eşit derecede geçerli olacak şekilde 2.0 ve 3.0 arasında seçim yapmak size kalmıştır.

Eksi sayılar için -2.1, -2.2, -2.3 ve -2.4, -2.0 olur; ve -2.6, 2.7, 2.8 ve 2.9 aritmetik yuvarlama altında -3.0 olur.

-2.5 için -2.0 ve -3.0 arasında bir seçim yapılması gerekir.

Diğer yuvarlama şekilleri

'Yuvarlama', ondalık basamaklı herhangi bir sayıyı alır ve bir sonraki 'tam' sayı yapar. Bu nedenle sadece 2,5 ve 2,6'yı 3,0'a değil, 2.1 ve 2.2'ye çevirir.

Yuvarlama, pozitif ve negatif sayıları sıfırdan uzaklaştırır. Örneğin. 2.5 ila 3.0 ve -2.5 ila -3.0.

'Yuvarlama' istenmeyen sayıları keserek sayıları kısaltır. Bunun, sayıları sıfıra taşıma etkisi vardır. Örneğin. 2,5 - 2,0 ve -2,5 - -2,0

"Bankacının yuvarlanması" nda - en yaygın haliyle - yuvarlanacak olan .5 yukarı veya aşağı yuvarlanır, böylece yuvarlamanın sonucu her zaman çift sayıdır. Böylece 2.5, 2.0, 3.5 ila 4.0, 4.5 ila 4.0, 5.5 ila 6.0 vb.

'Alternatif yuvarlama', yuvarlama ve yuvarlama arasındaki herhangi bir .5 için işlemi değiştirir.

'Rastgele yuvarlama', a5'i tamamen rastgele bir temelde yukarı veya aşağı yuvarlar.

Simetri ve asimetri

Yuvarlama işlevinin, tüm sayıları sıfırdan uzağa veya tüm sayıları sıfıra yuvarlarsa 'simetrik' olduğu söylenir.

Pozitif sayıları sıfıra ve negatif sayıları sıfırdan uzağa yuvarlarsa bir fonksiyon 'asimetrik' olur. Örn. 2,5 ila 2,0; ve -2.5 ila -3.0.

Ayrıca asimetrik, pozitif sayıları sıfırdan, negatif sayıları sıfıra yuvarlayan bir işlevdir. Örneğin. 2,5 ila 3,0; ve -2.5 ila -2.0.

Çoğu zaman insanlar simetrik yuvarlamayı düşünür, burada -2.5 -3.0 ve 3.5'e yuvarlanır ve 3.5 4.0'a yuvarlanır. (C # ileRound(AwayFromZero))


28

Varsayılan MidpointRounding.ToEvenveya Bankacıların yuvarlaması ( 2.5 2, 4.5 4 ve benzeri ) daha önce muhasebe raporları yazarken beni soktu, bu yüzden daha önce bulduğum şeyden birkaç kelime yazacağım ve buna bakarak bu gönderi.

Çift sayılara yuvarlanan bu bankacılar kimlerdir (belki İngiliz bankacılar!)?

Vikipedi sitesinden

Bankacıların yuvarlanma teriminin kökeni daha belirsizdir. Bu yuvarlama yönteminin bankacılıkta hiç bir standart olsaydı, kanıtların bulunması son derece zor oldu. Aksine, Avrupa Komisyonu raporunun 2. bölümü Euro'nun Tanıtımı ve Para Birimi Tutarlarının Yuvarlanması Daha önce bankacılıkta yuvarlamaya standart bir yaklaşım olmadığını göstermektedir; ve "yarı yol" miktarlarının yuvarlanması gerektiğini belirtir.

Tabii ki bankalar eşit miktarda mevduat almak için kullanmadığı sürece, özellikle bankacılık için çok garip bir yol gibi görünüyor. 2,4 milyon sterlin yatırın, ancak 2 milyon sterlin efendim.

IEEE Standard 754, 1985'e kadar uzanır ve her iki yuvarlama yolunu da verir, ancak bankacının standardın önerdiği şekilde. Bu wikipedia makalesi , dillerin yuvarlamayı nasıl uyguladığına dair uzun bir listeye sahiptir (aşağıdakilerden herhangi biri yanlışsa beni düzeltin) ve çoğu Bankacıları değil, okulda öğrettiğiniz yuvarlamayı kullanıyor:

  • C / C ++Math.h dosyasından round () sıfıra yuvarlar (bankanın yuvarlaması değil)
  • Java Math.Round sıfırdan uzağa yuvarlar (sonucu katlar, 0,5 ekler, bir tamsayıyı çevirir). Bir alternatif varBigDecimal'da
  • Perl , C'ye benzer bir yol kullanır
  • Javascript, Java Math.Round ile aynıdır.

Bilgi için teşekkürler. Bunu hiç fark etmedim. Milyonlarca alay konusu ile ilgili örneğiniz, ancak senti yuvarlasanız bile, 10 milyon banka hesabına faiz ödemek zorunda kalmanız, yarım sentlerin tümü yuvarlanırsa bankaya çok mal olacak veya tüm müşterilere çok mal olacak yarım sent aşağı yuvarlanır. Yani bunun kabul edilmiş standart olduğunu hayal edebiliyorum. Bunun gerçekten bankacılar tarafından kullanılıp kullanılmadığından emin değilim. Çoğu müşteri, çok para getirirken aşağı yuvarlandığını fark etmeyecek, ancak müşteri dostu yasalara sahip bir ülkede yaşıyorsanız bunun yasalarla zorunlu olduğunu hayal edebiliyorum
Harald Coppoolse

15

MSDN'den:

Varsayılan olarak, Math.Round MidpointRounding.ToEven kullanır. Çoğu insan alternatif olarak "eşit yuvarlama" yı bilmez, "sıfırdan yuvarlama" okulda daha yaygın olarak öğretilir. .NET, "sıfırdan yuvarlama" eğilimini yuvarlamadan biraz daha sık yuvarlama eğilimini paylaşmadığından (yuvarlanan sayıların pozitif olma eğilimi varsayarak) istatistiksel olarak üstün olduğu için varsayılan olarak "Düzleme Yuvarlama" yı kullanır. )

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


3

Silverlight, MidpointRounding seçeneğini desteklemediğinden kendiniz yazmanız gerekir. Gibi bir şey:

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;

}

Bunun bir uzantı olarak nasıl kullanılacağını içeren örnekler için şu gönderiye bakın: .NET ve Silverlight Yuvarlama


3

Benim C # uygulaması yoktu iken SQL sunucum 0.5 1 kadar yuvarlar bu sorun vardı. Böylece iki farklı sonuç görürsünüz.

İşte int / long ile bir uygulama. Java böyle yuvarlar.

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

Muhtemelen aklınıza gelebilecek en verimli yöntemdir.

Bunu iki katına çıkarmak ve ondalık duyarlık kullanmak istiyorsanız, o zaman gerçekten ondalık basamak sayısına göre 10'luk üsleri kullanmak meselesidir.

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

Ondalık basamaklar için negatif bir ondalık sayı girebilirsiniz ve bu kelime de iyi olur.

getRounding(239, -2) = 200


0

Bu yayında aradığınız cevap var:

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

Temel olarak söylediği bu:

Geri dönüş değeri

Basamağa eşit hassasiyetle en yakın sayı. Değer, biri çift diğeri tek olmak üzere iki sayının yarısındaysa, çift sayı döndürülür. Eğer değerin kesinliği rakamlardan küçükse, değer değişmeden döndürülür.

Bu yöntemin davranışı IEEE Standard 754, bölüm 4'ü izler. Bu tür yuvarlamaya bazen en yakın yuvarlama veya bankacının yuvarlanması denir. Rakamlar sıfırsa, bu tür yuvarlama bazen sıfıra yuvarlama olarak adlandırılır.


0

Silverlight, MidpointRounding seçeneğini desteklemez. İşte Silverlight için MidpointRounding numaralandırmasını ekleyen bir uzantı yöntemi:

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

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


-1

özel bir yuvarlama kullanarak

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

>.5aynı davranış üretir Math.Round. Soru, ondalık kısım tam olarak olduğunda ne olur 0.5. Math.Round, istediğiniz yuvarlama algoritmasını belirlemenizi sağlar
Panagiotis Kanavos

-2

Bu tüm cehennem gibi çirkin, ama her zaman doğru aritmetik yuvarlama üretir.

public double ArithRound(double number,int places){

  string numberFormat = "###.";

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

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

}

5
Arama Math.Roundve nasıl yuvarlanmasını istediğinizi belirtme de öyle.
yapılandırıcı

-2

İşte bu şekilde çalışmak zorunda:

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

1.905 ile 2 ondalık basamağa çalışılması beklendiği gibi 1.91, fakat Math.Round(1.905,2,MidpointRounding.AwayFromZero)1.90 verir! Math.Round yöntemi, programcıların karşılaşabileceği temel sorunların çoğu için kesinlikle tutarsız ve kullanılamaz. Çünkü (int) 1.905 * decimalPowerOfTen = Math.Round(number * decimalPowerOfTen, 2)aşağı yuvarlanması gereken şeyleri yuvarlamak istemiyorum.


Math.Round(1.905,2,MidpointRounding.AwayFromZero)döner1.91
Panagiotis Kanavos
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.