Saya memiliki kode sederhana berikut:
int speed1 = (int)(6.2f * 10);
float tmp = 6.2f * 10;
int speed2 = (int)tmp;
speed1
dan speed2
harus memiliki nilai yang sama, tetapi pada kenyataannya, saya punya:
speed1 = 61
speed2 = 62
Saya tahu saya mungkin harus menggunakan Math.Round daripada casting, tapi saya ingin mengerti mengapa nilainya berbeda.
Saya melihat bytecode yang dihasilkan, tetapi kecuali store dan load, opcodesnya sama.
Saya juga mencoba kode yang sama di java, dan saya mendapatkan 62 dan 62 dengan benar.
Adakah yang bisa menjelaskan hal ini?
Sunting: Dalam kode asli, itu tidak langsung 6.2f * 10 tetapi panggilan fungsi * sebuah konstanta. Saya memiliki bytecode berikut:
untuk speed1
:
IL_01b3: ldloc.s V_8
IL_01b5: callvirt instance float32 myPackage.MyClass::getSpeed()
IL_01ba: ldc.r4 10.
IL_01bf: mul
IL_01c0: conv.i4
IL_01c1: stloc.s V_9
untuk speed2
:
IL_01c3: ldloc.s V_8
IL_01c5: callvirt instance float32 myPackage.MyClass::getSpeed()
IL_01ca: ldc.r4 10.
IL_01cf: mul
IL_01d0: stloc.s V_10
IL_01d2: ldloc.s V_10
IL_01d4: conv.i4
IL_01d5: stloc.s V_11
kita dapat melihat bahwa operan mengapung dan satu-satunya perbedaan adalah stloc/ldloc
.
Sedangkan untuk mesin virtual, saya mencoba dengan Mono / Win7, Mono / MacOS, dan .NET / Windows, dengan hasil yang sama.
sumber
Jawaban:
Pertama-tama, saya berasumsi bahwa Anda tahu bahwa
6.2f * 10
itu bukan 62 karena pembulatan titik mengambang (itu sebenarnya nilai 61.99999809265137 ketika dinyatakan sebagai adouble
) dan bahwa pertanyaan Anda hanya tentang mengapa dua perhitungan yang tampaknya identik menghasilkan nilai yang salah.Jawabannya adalah dalam kasus
(int)(6.2f * 10)
, Anda mengambildouble
nilai 61.99999809265137 dan memotongnya menjadi bilangan bulat, yang menghasilkan 61.Dalam kasus
float f = 6.2f * 10
, Anda mengambil nilai ganda 61.99999809265137 dan membulatkan ke terdekatfloat
, yaitu 62. Anda kemudian memotongnyafloat
menjadi bilangan bulat, dan hasilnya adalah 62.Latihan: Jelaskan hasil dari urutan operasi berikut.
Pembaruan: Seperti yang tercantum dalam komentar, ekspresi
6.2f * 10
secara formal afloat
karena parameter kedua memiliki konversi implisitfloat
yang lebih baik daripada konversi implisitdouble
.Masalah sebenarnya adalah bahwa kompiler diizinkan (tetapi tidak diharuskan) untuk menggunakan perantara yang presisi lebih tinggi dari tipe formal (bagian 11.2.2) . Itu sebabnya Anda melihat perilaku yang berbeda pada sistem yang berbeda: Dalam ekspresi
(int)(6.2f * 10)
, kompiler memiliki opsi untuk menjaga nilai6.2f * 10
dalam bentuk perantara presisi tinggi sebelum dikonversi keint
. Jika ya, maka hasilnya adalah 61. Jika tidak, maka hasilnya adalah 62.Dalam contoh kedua, tugas eksplisit untuk
float
memaksa pembulatan terjadi sebelum konversi ke bilangan bulat.sumber
(int)(6.2f * 10)
mengambildouble
nilai, seperti yangf
ditentukan itufloat
? Saya pikir poin utama (masih belum terjawab) ada di sini.6.2f * 10
sebenarnyafloat
, bukandouble
. Saya pikir kompiler mengoptimalkan perantara, sebagaimana diizinkan oleh paragraf terakhir 11.1.6 .float
konversi terlebih dahulu.Deskripsi
Angka mengambang jarang tepat.
6.2f
adalah sesuatu seperti6.1999998...
. Jika Anda melemparkan ini ke int, itu akan memotongnya dan ini * 10 menghasilkan 61.Lihat
DoubleConverter
kelas Jon Skeets . Dengan kelas ini Anda benar-benar dapat memvisualisasikan nilai angka mengambang sebagai string.Double
danfloat
keduanya angka mengambang , desimal tidak (itu adalah angka titik tetap).Sampel
Informasi Lebih Lanjut
sumber
Lihatlah IL:
Compiler mengurangi ekspresi konstanta waktu kompilasi ke nilai konstannya, dan saya pikir itu membuat perkiraan yang salah di beberapa titik ketika ia mengubah konstanta menjadi
int
. Dalam kasusspeed2
, konversi ini dibuat bukan oleh kompiler, tetapi oleh CLR, dan mereka tampaknya menerapkan aturan yang berbeda ...sumber
Dugaan saya adalah bahwa
6.2f
representasi nyata dengan pelampung presisi6.1999999
sementara62f
mungkin sesuatu yang mirip dengan62.00000001
.(int)
casting selalu memotong nilai desimal jadi itu sebabnya Anda mendapatkan perilaku itu.EDIT : Menurut komentar saya telah mengubah perilaku
int
casting ke definisi yang jauh lebih tepat.sumber
int
memotong nilai desimal, itu tidak bulat.float
->int
melibatkan pembulatan. = DSaya mengkompilasi dan membongkar kode ini (pada Win7 / .NET 4.0). Saya kira kompilator mengevaluasi ekspresi konstanta mengambang sebagai dobel.
sumber
Single
hanya mempertahankan 7 digit dan ketika casting keInt32
kompiler memotong semua angka floating point. Selama konversi satu atau lebih digit signifikan dapat hilang.memberikan hasil 619999980 sehingga (Int32) (6.2f * 10) memberikan 61.
Ini berbeda ketika dua Single dikalikan, dalam hal ini tidak ada operasi terpotong tetapi hanya perkiraan.
Lihat http://msdn.microsoft.com/en-us/library/system.single.aspx
sumber
Apakah ada alasan Anda mengetik casting untuk
int
bukannya parsing?akan membaca
Perbedaannya mungkin berkaitan dengan pembulatan: jika Anda melemparkan ke
double
Anda mungkin akan mendapatkan sesuatu seperti 61.78426.Harap perhatikan output berikut
Itu sebabnya Anda mendapatkan nilai yang berbeda!
sumber
Int.Parse
mengambil string sebagai parameter.