Mengapa Math.Round (2.5) mengembalikan 2 bukannya 3?

416

Di C #, hasilnya Math.Round(2.5)adalah 2.

Seharusnya 3, bukan? Kenapa hanya 2 di C #?

jeffu
sumber
5
Ini sebenarnya fitur. Lihat <a href=" msdn.microsoft.com/en-us/library/... Dokumentasi MSDN</a> . Pembulatan semacam ini dikenal sebagai pembulatan bankir. Adapun solusinya, ada <a href = " msdn. microsoft.com/en-us/library/… overload </a> yang memungkinkan penelepon menentukan cara melakukan pembulatan.
Joe
1
Rupanya metode putaran, ketika diminta untuk membulatkan angka persis antara dua bilangan bulat, mengembalikan bilangan bulat genap. Jadi, Math.Round (3.5) mengembalikan 4. Lihat artikel ini
Matthew Jones
20
Math.Round(2.5, 0, MidpointRounding.AwayFromZero);
Robert Durgin
SQL Server berputar seperti itu; hasil tes menarik ketika ada tes unit C # ti memvalidasi pembulatan dilakukan dalam T-SQL.
idstam
7
@ nama itu bukan bug. Ini adalah cara floating point biner bekerja. 1.005tidak dapat direpresentasikan secara persis ganda. Itu mungkin 1.00499.... Jika Anda menggunakan Decimalmasalah ini akan hilang. Keberadaan Math.Round berlebihan yang mengambil sejumlah digit desimal pada dobel adalah pilihan desain IMO yang meragukan, karena jarang akan bekerja dengan cara yang bermakna.
CodesInChaos

Jawaban:

561

Pertama, ini bukan bug C # - itu akan menjadi bug .NET. C # adalah bahasanya - tidak memutuskan bagaimana Math.Rounddiimplementasikan.

Dan kedua, tidak - jika Anda membaca dokumen , Anda akan melihat bahwa pembulatan standar adalah "pembulatan ke genap" (pembulatan bankir):

Return Value
Type: System.Double
Integer terdekat a. Jika komponen fraksional dari a adalah setengah antara dua bilangan bulat, salah satunya genap dan yang lain ganjil, maka bilangan genap dikembalikan. Perhatikan bahwa metode ini mengembalikan tipe Doublebukan integral.

Keterangan
Perilaku metode ini mengikuti IEEE Standard 754, bagian 4. Pembulatan semacam ini kadang-kadang disebut pembulatan ke terdekat, atau pembulatan bankir. Ini meminimalkan kesalahan pembulatan yang dihasilkan dari pembulatan nilai titik tengah secara konsisten dalam satu arah.

Anda dapat menentukan bagaimana Math.Roundseharusnya membulatkan titik tengah menggunakan kelebihan yang mengambil MidpointRoundingnilai. Ada satu kelebihan dengan yang MidpointRoundingterkait dengan masing-masing kelebihan yang tidak memilikinya:

Apakah default ini dipilih dengan baik atau tidak adalah masalah yang berbeda. ( MidpointRoundinghanya diperkenalkan di .NET 2.0. Sebelum itu saya tidak yakin ada cara mudah untuk menerapkan perilaku yang diinginkan tanpa melakukannya sendiri.) Secara khusus, sejarah telah menunjukkan bahwa itu bukan perilaku yang diharapkan - dan dalam kebanyakan kasus itu adalah dosa utama dalam desain API. Saya bisa melihat mengapa Pembulatan Banker berguna ... tetapi masih mengejutkan banyak orang.

Anda mungkin tertarik untuk melihat enum ( RoundingMode) yang setara dengan Java terdekat yang menawarkan lebih banyak opsi. (Itu tidak hanya berurusan dengan titik tengah.)

Jon Skeet
sumber
4
saya tidak tahu apakah ini bug, saya pikir itu adalah desain sejak 0,5 adalah sedekat dengan bilangan bulat terendah terdekat dengan bilangan bulat tertinggi terdekat.
Stan R.
3
Saya ingat perilaku ini di VB sebelum .NET diterapkan.
John Fiala
7
Memang, IEEE Standard 754, bagian 4 sebagai negara dokumentasi.
Jon Skeet
2
Saya terbakar oleh ini beberapa waktu yang lalu dan berpikir itu adalah kegilaan belaka juga. Untungnya mereka menambahkan cara untuk menentukan pembulatan yang kita semua pelajari di sekolah dasar; MidPointRounding.
Shea
26
+1 untuk "itu bukan perilaku yang diharapkan [...] itu adalah dosa utama dalam desain API"
BlueRaja - Danny Pflughoeft
215

Itu disebut pembulatan ke genap (atau pembulatan bankir), yang merupakan strategi pembulatan yang valid untuk meminimalkan kesalahan dalam jumlah (MidpointRounding.ToEven). Teorinya adalah bahwa, jika Anda selalu membulatkan angka 0,5 ke arah yang sama, kesalahan akan bertambah lebih cepat (round-to-even seharusnya meminimalkan itu) (a) .

Ikuti tautan ini untuk deskripsi MSDN tentang:

  • Math.Floor, yang membulatkan ke arah infinity negatif.
  • Math.Ceiling, yang dibulatkan ke arah infinity positif.
  • Math.Truncate, yang membulatkan ke atas atau ke bawah menuju nol.
  • Math.Round, yang membulatkan ke bilangan bulat terdekat atau jumlah tempat desimal yang ditentukan. Anda dapat menentukan perilaku jika persis sama antara dua kemungkinan, seperti pembulatan sehingga angka akhir genap (" Round(2.5,MidpointRounding.ToEven)" menjadi 2) atau sehingga jauh dari nol (" Round(2.5,MidpointRounding.AwayFromZero)" menjadi 3).

Diagram dan tabel berikut dapat membantu:

-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

Perhatikan bahwa Roundjauh lebih kuat daripada yang terlihat, hanya karena dapat membulatkan ke tempat desimal tertentu. Semua yang lain membulatkan ke nol desimal selalu. Sebagai contoh:

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

Dengan fungsi-fungsi lain, Anda harus menggunakan tipuan multiply / bagi untuk mendapatkan efek yang sama:

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

(a) Tentu saja, teori itu tergantung pada fakta bahwa data Anda memiliki penyebaran nilai yang merata di belahan genap (0,5, 2.5, 4.5, ...) dan setengah aneh (1,5, 3,5, ...).

Jika semua "nilai setengah" adalah genap (misalnya), kesalahan akan terakumulasi secepat jika Anda selalu dibulatkan ke atas.

paxdiablo
sumber
3
Juga dikenal sebagai Banker's Rounding
Pondidum
Penjelasan yang bagus! Saya ingin melihat sendiri bagaimana kesalahan terakumulasi dan saya menulis sebuah skrip yang menunjukkan bahwa nilai dibulatkan menggunakan pembulatan bankir, dalam jangka panjang, memiliki jumlah dan rata-rata mereka lebih dekat dengan nilai-nilai asli. github.com/AmadeusW/RoundingDemo (gambar plot tersedia)
Amadeusz Wieczorek
Hanya beberapa saat setelahnya: bukankah seharusnya ecentang (= 2.8) lebih baik dari pada 2centang?
superjos
Cara mudah untuk mengingat, dan mengasumsikan tempat persepuluhan adalah 5: - tempat dan kesepuluh semuanya ganjil = bulat - tempat dan kesepuluh bercampur = bulat bawah * Nol tidak aneh * Dibalik untuk angka negatif
Arkham Angel
@ArkhamAngel, itu sepertinya benar-benar lebih sulit untuk diingat daripada hanya "membuat angka terakhir" :-)
paxdiablo
42

Dari MSDN, Math.Round (double a) mengembalikan:

Bilangan bulat terdekat a. Jika komponen fraksional dari a adalah setengah antara dua bilangan bulat, salah satunya genap dan yang lain ganjil, maka bilangan genap dikembalikan.

... dan 2.5, berada di tengah-tengah antara 2 dan 3, dibulatkan ke angka genap (2). ini disebut Pembulatan Banker (atau round-to-even), dan merupakan standar pembulatan yang umum digunakan.

Artikel MSDN yang sama:

Perilaku metode ini mengikuti IEEE Standard 754, bagian 4. Pembulatan semacam ini kadang-kadang disebut pembulatan ke terdekat, atau pembulatan bankir. Ini meminimalkan kesalahan pembulatan yang dihasilkan dari pembulatan nilai titik tengah secara konsisten dalam satu arah.

Anda dapat menentukan perilaku pembulatan yang berbeda dengan memanggil kelebihan Math.Round yang mengambil MidpointRoundingmode.

Michael Petrotta
sumber
37

Anda harus memeriksa MSDN untuk Math.Round:

Perilaku metode ini mengikuti IEEE Standard 754, bagian 4. Pembulatan semacam ini kadang-kadang disebut pembulatan ke terdekat, atau pembulatan bankir.

Anda dapat menentukan perilaku Math.Roundmenggunakan kelebihan:

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

Math.Round(2.5, 0, MidpointRounding.ToEven); // gives 2
Dirk Vollmar
sumber
31

Sifat pembulatan

Pertimbangkan tugas pembulatan bilangan yang berisi sebagian kecil, misalnya, bilangan bulat. Proses pembulatan dalam keadaan ini adalah untuk menentukan bilangan bulat mana yang paling tepat mewakili bilangan yang Anda bulatkan.

Secara umum, atau pembulatan 'aritmatika', jelas bahwa putaran 2.1, 2.2, 2.3 dan 2.4 menjadi 2.0; dan 2.6, 2.7, 2.8 dan 2.9 hingga 3.0.

Itu berarti 2.5, yang tidak lebih dekat ke 2.0 daripada ke 3.0. Terserah Anda untuk memilih antara 2.0 dan 3.0, keduanya akan sama-sama valid.

Untuk angka minus, -2.1, -2.2, -2.3 dan -2.4, akan menjadi -2.0; dan -2.6, 2.7, 2.8 dan 2.9 akan menjadi -3.0 di bawah pembulatan aritmatika.

Untuk -2.5 dibutuhkan pilihan antara -2.0 dan -3.0.

Bentuk pembulatan lainnya

'Pembulatan' mengambil angka apa pun dengan tempat desimal dan menjadikannya nomor 'keseluruhan' berikutnya. Dengan demikian, tidak hanya 2,5 dan 2,6 putaran ke 3,0, tetapi begitu juga 2,1 dan 2,2.

Pembulatan memindahkan angka positif dan negatif dari nol. Misalnya. 2,5 hingga 3,0 dan -2,5 hingga -3,0.

'Membulatkan ke bawah' memotong angka dengan memotong angka yang tidak diinginkan. Ini memiliki efek memindahkan angka ke nol. Misalnya. 2,5 hingga 2,0 dan -2,5 hingga -2,0

Dalam "pembulatan bankir" - dalam bentuknya yang paling umum - .5 yang akan dibulatkan dibulatkan ke atas atau ke bawah sehingga hasil pembulatan selalu berupa bilangan genap. Jadi 2,5 putaran ke 2.0, 3.5 ke 4.0, 4.5 ke 4.0, 5.5 ke 6.0, dan seterusnya.

'Alternate rounding' sebagai pengganti proses untuk 0,5 antara pembulatan ke bawah dan pembulatan ke atas.

'Pembulatan acak' melingkar .5 ke atas atau ke bawah secara acak.

Simetri dan asimetri

Fungsi pembulatan dikatakan 'simetris' jika ia membulatkan semua angka dari nol atau membulatkan semua angka ke nol.

Fungsi adalah 'asimetris' jika membulatkan angka positif ke nol dan angka negatif menjauh dari nol .. Misalnya. 2,5 hingga 2,0; dan -2,5 hingga -3,0.

Juga asimetris adalah fungsi yang membulatkan angka positif dari nol dan angka negatif ke nol. Misalnya. 2,5 hingga 3,0; dan -2,5 hingga -2,0.

Sebagian besar waktu orang berpikir tentang pembulatan simetris, di mana -2.5 akan dibulatkan ke -3.0 dan 3.5 akan dibulatkan ke 4.0. (dalam C #Round(AwayFromZero))

Patrick Peters
sumber
28

Defaultnya MidpointRounding.ToEven, atau pembulatan Bankir ( 2,5 menjadi 2, 4,5 menjadi 4 dan seterusnya ) telah menyengat saya sebelumnya dengan menulis laporan untuk akuntansi, jadi saya akan menulis beberapa kata dari apa yang saya temukan, sebelumnya dan dari melihat ke dalamnya untuk posting ini.

Siapa saja bankir yang mengumpulkan angka genap (mungkin bankir Inggris!)?

Dari wikipedia

Asal usul istilah pembulatan bankir masih lebih jelas. Jika metode pembulatan ini pernah menjadi standar dalam perbankan, bukti telah terbukti sangat sulit ditemukan. Sebaliknya, bagian 2 dari laporan Komisi Eropa Pengenalan Euro dan Jumlah Pembulatan Mata Uang menunjukkan bahwa sebelumnya tidak ada pendekatan standar untuk pembulatan di perbankan; dan itu menentukan bahwa jumlah "setengah jalan" harus dibulatkan.

Tampaknya cara pembulatan yang sangat aneh terutama untuk perbankan, kecuali tentu saja bank menggunakan untuk menerima banyak simpanan dalam jumlah genap. Setor £ 2,4 juta, tapi kami akan menyebutnya £ 2 juta, pak.

Standar IEEE 754 tanggal kembali ke 1985 dan memberikan kedua cara pembulatan, tetapi dengan bankir sebagai yang direkomendasikan oleh standar. Ini artikel wikipedia memiliki daftar panjang tentang bagaimana bahasa menerapkan pembulatan (saya benar jika salah satu di bawah salah) dan yang paling tidak menggunakan Bankers' tetapi pembulatan Anda diajarkan di sekolah:

  • Putaran C / C ++ () dari math.h putaran jauh dari nol (bukan pembulatan bankir)
  • Java Math. Putaran bulat dari nol (itu lantai hasilnya, menambahkan 0,5, dilemparkan ke bilangan bulat). Ada alternatif di BigDecimal
  • Perl menggunakan cara yang mirip dengan C
  • Javascript sama dengan Java's Math.Round.
Chris S
sumber
Terima kasih untuk informasi. Saya tidak pernah menyadari ini. Contoh Anda tentang jutaan ejekan itu sedikit, tetapi bahkan jika Anda membulatkan sen, harus membayar bunga pada 10 juta rekening bank akan banyak biaya bank jika semua setengah sen dibulatkan, atau akan banyak biaya klien jika semua setengah sen dibulatkan ke bawah. Jadi saya bisa membayangkan ini adalah standar yang disepakati. Tidak yakin apakah ini benar-benar digunakan oleh para bankir. Sebagian besar pelanggan tidak akan melihat pembulatan, sambil membawa banyak uang, tapi saya bisa membayangkan ini diwajibkan oleh undang-undang jika Anda tinggal di negara dengan undang
Harald Coppoolse
15

Dari MSDN:

Secara default, Math.Round menggunakan MidpointRounding.ToEven. Kebanyakan orang tidak terbiasa dengan "pembulatan ke genap" sebagai alternatif, "pembulatan dari nol" lebih umum diajarkan di sekolah. .NET default ke "Pembulatan ke genap" karena secara statistik lebih unggul karena tidak berbagi kecenderungan "pembulatan dari nol" untuk mengumpulkan sedikit lebih sering daripada membulatkan ke bawah (dengan asumsi angka yang dibulatkan cenderung positif. )

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

Cristian Donoso
sumber
3

Karena Silverlight tidak mendukung opsi MidpointRounding, Anda harus menulis sendiri. Sesuatu seperti:

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;

}

Untuk contoh termasuk cara menggunakan ini sebagai ekstensi lihat posting: .NET dan Silverlight Rounding

JBrooks
sumber
3

Saya punya masalah ini di mana server SQL saya mengumpulkan 0,5 hingga 1 sedangkan aplikasi C # saya tidak. Jadi, Anda akan melihat dua hasil yang berbeda.

Inilah implementasi dengan int / long. Ini adalah bagaimana putaran Jawa.

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

Ini mungkin metode yang paling efisien yang dapat Anda pikirkan juga.

Jika Anda ingin membuatnya ganda dan menggunakan presisi desimal, maka itu benar-benar hanya masalah menggunakan eksponen 10 berdasarkan berapa banyak tempat desimal.

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

Anda dapat memasukkan desimal negatif untuk titik desimal dan kata itu juga baik.

getRounding(239, -2) = 200
ShortFuse
sumber
1

Cara sederhana adalah:

Math.Ceiling(decimal.Parse(yourNumber + ""));
Nalan Madheswaran
sumber
0

Posting ini memiliki jawaban yang Anda cari:

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

Pada dasarnya inilah yang dikatakan:

Nilai Pengembalian

Nilai angka terdekat dengan presisi sama dengan angka. Jika nilainya setengah di antara dua angka, yang satu genap dan ganjil lainnya, maka angka genap dikembalikan. Jika ketepatan nilai kurang dari digit, maka nilai dikembalikan tidak berubah.

Perilaku metode ini mengikuti IEEE Standard 754, bagian 4. Pembulatan semacam ini kadang-kadang disebut pembulatan ke terdekat, atau pembulatan bankir. Jika angka nol, pembulatan semacam ini kadang-kadang disebut pembulatan ke nol.

Gunung berapi
sumber
0

Silverlight tidak mendukung opsi MidpointRounding. Berikut adalah metode ekstensi untuk Silverlight yang menambahkan enum 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;
        }
    }
}

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

jascur2
sumber
-1

menggunakan pembulatan khusus

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);
}
anand360
sumber
>.5menghasilkan perilaku yang sama dengan Math.Round. Pertanyaannya adalah apa yang terjadi ketika bagian desimal tepat 0.5. Math.Round memungkinkan Anda menentukan jenis algoritme pembulatan yang Anda inginkan
Panagiotis Kanavos
-2

Ini jelek sekali, tapi selalu menghasilkan pembulatan aritmatika yang benar.

public double ArithRound(double number,int places){

  string numberFormat = "###.";

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

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

}
James Montagne
sumber
5
Begitu juga dengan menelepon Math.Rounddan menentukan bagaimana Anda ingin membulatkannya.
konfigurator
-2

Inilah cara saya harus mengatasinya:

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

Mencoba dengan 1,905 dengan 2 desimal akan memberikan 1,91 seperti yang diharapkan tetapi Math.Round(1.905,2,MidpointRounding.AwayFromZero)memberi 1,90! Metode Math.Round benar-benar tidak konsisten dan tidak dapat digunakan untuk sebagian besar masalah dasar yang mungkin dihadapi programmer. Saya harus memeriksa apakah (int) 1.905 * decimalPowerOfTen = Math.Round(number * decimalPowerOfTen, 2)karena saya tidak ingin mengumpulkan apa yang harus dibulatkan.

MikeMuffinMan
sumber
Math.Round(1.905,2,MidpointRounding.AwayFromZero)kembali1.91
Panagiotis Kanavos