Dalam program berikut ini Anda dapat melihat bahwa setiap nilai sedikit kurang dari .5
dibulatkan, kecuali untuk 0.5
.
for (int i = 10; i >= 0; i--) {
long l = Double.doubleToLongBits(i + 0.5);
double x;
do {
x = Double.longBitsToDouble(l);
System.out.println(x + " rounded is " + Math.round(x));
l--;
} while (Math.round(x) > i);
}
cetakan
10.5 rounded is 11
10.499999999999998 rounded is 10
9.5 rounded is 10
9.499999999999998 rounded is 9
8.5 rounded is 9
8.499999999999998 rounded is 8
7.5 rounded is 8
7.499999999999999 rounded is 7
6.5 rounded is 7
6.499999999999999 rounded is 6
5.5 rounded is 6
5.499999999999999 rounded is 5
4.5 rounded is 5
4.499999999999999 rounded is 4
3.5 rounded is 4
3.4999999999999996 rounded is 3
2.5 rounded is 3
2.4999999999999996 rounded is 2
1.5 rounded is 2
1.4999999999999998 rounded is 1
0.5 rounded is 1
0.49999999999999994 rounded is 1
0.4999999999999999 rounded is 0
Saya menggunakan pembaruan Java 6 31.
java
floating-point
double
rounding
Peter Lawrey
sumber
sumber
0.5
nomor dan kemudian menggunakanfloor
; Java 7 tidak lagi mendokumentasikannya seperti itu (mungkin / semoga karena mereka memperbaikinya).Jawaban:
Ringkasan
Di Jawa 6 (dan mungkin sebelumnya),
round(x)
diimplementasikan sebagaifloor(x+0.5)
. 1 Ini adalah bug spesifikasi, untuk kasus patologis yang tepat ini. 2 Java 7 tidak lagi mengamanatkan implementasi yang rusak ini. 3Masalah
0,5 + 0,49999999999999994 tepat 1 dalam presisi ganda:
Ini karena 0.4999999999999999994 memiliki eksponen yang lebih kecil dari 0,5, jadi ketika mereka ditambahkan, mantissa-nya berubah, dan ULP semakin besar.
Solusinya
Sejak Java 7, OpenJDK (misalnya) mengimplementasikannya sebagai berikut: 4
1. http://docs.oracle.com/javase/6/docs/api/java/lang/Math.html#round%28double%29
2. http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6430675 (kredit ke @SimonNickerson karena menemukan ini)
3. http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html#round%28double%29
4. http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/java/lang/Math.java#Math.round%28double%29
sumber
round
di Javadoc untukMath.round
atau dalam ikhtisarMath
kelas.Tampaknya ini adalah bug yang dikenal ( Java bug 6430675: Math.round memiliki perilaku mengejutkan untuk 0x1.fffffffffffffp-2 ) yang telah diperbaiki di Java 7.
sumber
Kode sumber dalam JDK 6:
Kode sumber di JDK 7:
Ketika nilainya 0,4999999999999999994d, dalam JDK 6, itu akan memanggil lantai dan karenanya mengembalikan 1, tetapi dalam JDK 7,
if
kondisinya sedang memeriksa apakah angka tersebut adalah nilai ganda terbesar kurang dari 0,5 atau tidak. Seperti dalam kasus ini angkanya bukan nilai ganda terbesar kurang dari 0,5, sehinggaelse
blok mengembalikan 0.Anda dapat mencoba 0,49999999999999999d, yang akan mengembalikan 1, tetapi bukan 0, karena ini adalah nilai ganda terbesar kurang dari 0,5.
sumber
floor
metode membulatkannya dengan benar.Saya mendapatkan hal yang sama pada JDK 1.6 32-bit, tetapi pada Java 7 64-bit saya mendapatkan 0 untuk 0.4999999999999999994 yang dibulatkan adalah 0 dan baris terakhir tidak dicetak. Tampaknya menjadi masalah VM, namun, menggunakan floating point, Anda harus mengharapkan hasilnya sedikit berbeda pada berbagai lingkungan (CPU, mode 32- atau 64-bit).
Dan, ketika menggunakan
round
atau membalikkan matriks, dll., Bit ini dapat membuat perbedaan besar.output x64:
sumber
Jawaban selanjutnya adalah kutipan dari laporan bug Oracle di 6430675 . Kunjungi laporan untuk penjelasan lengkap.
Metode {Math, StrictMath.round secara operasional didefinisikan sebagai
untuk argumen ganda. Sementara definisi ini biasanya berfungsi seperti yang diharapkan, ini memberikan hasil yang mengejutkan dari 1, bukan 0, untuk 0x1.fffffffffffffp-2 (0.4999999999999999994).
Nilai 0,49999999999999994 adalah nilai floating-point terbesar kurang dari 0,5. Sebagai heksadesimal floating-point literal nilainya adalah 0x1.fffffffffffffp-2, yang sama dengan (2 - 2 ^ 52) * 2 ^ -2. == (0,5 - 2 ^ 54). Oleh karena itu, nilai penjumlahannya tepat
adalah 1 - 2 ^ 54. Ini adalah setengah antara dua angka floating-point yang berdekatan (1 - 2 ^ 53) dan 1. Dalam putaran aritmetika IEEE 754 ke mode pembulatan genap terdekat yang digunakan oleh Java, ketika hasil floating-point tidak tepat, semakin dekat keduanya nilai floating-point yang dapat diwakili yang mengurung hasil yang tepat harus dikembalikan; jika kedua nilai sama-sama dekat, nilai bit nol terakhirnya dikembalikan. Dalam hal ini nilai balik yang benar dari tambah adalah 1, bukan nilai terbesar kurang dari 1.
Sementara metode beroperasi sebagaimana didefinisikan, perilaku pada input ini sangat mengejutkan; spesifikasi dapat diubah menjadi sesuatu yang lebih seperti "Putaran ke panjang terdekat, ikatan pembulatan ke atas," yang akan memungkinkan perilaku pada input ini diubah.
sumber