Mengapa mengubah jumlah pesanan mengembalikan hasil yang berbeda?
23.53 + 5.88 + 17.64
= 47.05
23.53 + 17.64 + 5.88
= 47.050000000000004
Kedua Java dan JavaScript mengembalikan hasil yang sama.
Saya mengerti bahwa, karena cara angka floating point direpresentasikan dalam biner, beberapa bilangan rasional ( seperti 1/3 - 0,333333 ... ) tidak dapat direpresentasikan secara tepat.
Mengapa hanya mengubah urutan elemen mempengaruhi hasilnya?
java
javascript
floating-point
Marlon Bernardes
sumber
sumber
(2.0^53 + 1) - 1 == 2.0^53 - 1 != 2^53 == 2^53 + (1 - 1)
). Karenanya, ya: berhati-hatilah saat memilih urutan jumlah dan operasi lainnya. Beberapa bahasa menyediakan built-in untuk melakukan penjumlahan "presisi tinggi" (misalnya pythonmath.fsum
), jadi Anda dapat mempertimbangkan untuk menggunakan fungsi-fungsi ini alih-alih algoritma jumlah naif.Jawaban:
Ini akan mengubah titik di mana nilai dibulatkan, berdasarkan besarnya mereka. Sebagai contoh dari jenis hal yang kita lihat, mari kita berpura-pura bahwa alih-alih binary floating point, kita menggunakan tipe floating point desimal dengan 4 digit signifikan, di mana setiap penambahan dilakukan pada presisi "tak terbatas" dan kemudian dibulatkan menjadi nomor representatif terdekat. Berikut ini dua jumlah:
Kami bahkan tidak perlu non-integer untuk ini menjadi masalah:
Ini menunjukkan kemungkinan yang lebih jelas bahwa bagian yang penting adalah kita memiliki jumlah digit signifikan yang terbatas - bukan jumlah desimal yang terbatas . Jika kita selalu bisa menjaga jumlah tempat desimal yang sama, maka setidaknya dengan penambahan dan pengurangan, kita akan baik-baik saja (asalkan nilainya tidak meluap). Masalahnya adalah ketika Anda mencapai angka yang lebih besar, informasi yang lebih kecil hilang - 10001 dibulatkan menjadi 10.000 dalam kasus ini. (Ini adalah contoh masalah yang dicatat Eric Lippert dalam jawabannya .)
Penting untuk dicatat bahwa nilai pada baris pertama dari sisi kanan adalah sama dalam semua kasus - jadi meskipun penting untuk memahami bahwa angka desimal Anda (23.53, 5.88, 17.64) tidak akan direpresentasikan persis seperti
double
nilai, itu hanya masalah karena masalah yang ditunjukkan di atas.sumber
May extend this later - out of time right now!
menunggu dengan penuh semangat untuk itu @Jondouble
danfloat
, di mana untuk angka yang sangat besar, angka representatif berturut-turut terpisah lebih dari 1.Inilah yang terjadi dalam biner. Seperti yang kita ketahui, beberapa nilai floating-point tidak dapat direpresentasikan secara tepat dalam biner, bahkan jika mereka dapat direpresentasikan secara tepat dalam desimal. 3 angka ini hanyalah contoh dari fakta itu.
Dengan program ini saya menampilkan representasi heksadesimal dari setiap angka dan hasil dari setiap penambahan.
The
printValueAndInHex
Metode ini hanya pembantu hex-printer.Outputnya adalah sebagai berikut:
4 angka pertama adalah
x
,y
,z
, dans
's representasi heksadesimal. Dalam representasi floating point IEEE, bit 2-12 mewakili eksponen biner , yaitu skala angka. (Bit pertama adalah bit tanda, dan bit yang tersisa untuk mantissa .) Eksponen yang diwakili sebenarnya adalah angka biner minus 1023.Eksponen untuk 4 angka pertama diekstraksi:
Set tambahan pertama
Angka kedua (
y
) besarnya lebih kecil. Saat menambahkan dua angka ini untuk mendapatkanx + y
, 2 bit terakhir dari angka kedua (01
) digeser keluar dari jangkauan dan tidak termasuk dalam perhitungan.Penambahan kedua menambah
x + y
danz
dan menambahkan dua angka dari skala yang sama.Set tambahan kedua
Di sini,
x + z
terjadi dulu. Mereka memiliki skala yang sama, tetapi mereka menghasilkan angka yang lebih tinggi dalam skala:Penambahan kedua menambahkan
x + z
dany
, dan sekarang 3 bit dijatuhkan dariy
untuk menambahkan angka (101
). Di sini, harus ada putaran ke atas, karena hasilnya adalah angka floating point berikutnya:4047866666666666
untuk set tambahan pertama vs4047866666666667
untuk set tambahan kedua. Kesalahan itu cukup signifikan untuk ditampilkan dalam cetakan total.Kesimpulannya, berhati-hatilah saat melakukan operasi matematika pada nomor IEEE. Beberapa representasi tidak tepat, dan mereka menjadi lebih tidak eksak ketika skalanya berbeda. Tambahkan dan kurangi angka dengan skala yang sama jika Anda bisa.
sumber
=)
+1 untuk pembantu hex-printer Anda ... itu sangat rapi!Jawaban Jon tentu saja benar. Dalam kasus Anda kesalahan tidak lebih besar dari kesalahan yang Anda akumulasikan dengan melakukan operasi floating point sederhana. Anda punya skenario di mana dalam satu kasus Anda mendapatkan kesalahan nol dan dalam kasus lain Anda mendapatkan kesalahan kecil; itu sebenarnya bukan skenario yang menarik. Pertanyaan yang bagus adalah: adakah skenario di mana mengubah urutan perhitungan berubah dari kesalahan kecil menjadi kesalahan relatif besar? Jawabannya jelas, ya.
Pertimbangkan misalnya:
vs.
vs.
Jelas dalam aritmatika yang tepat mereka akan sama. Sangat menyenangkan untuk mencoba menemukan nilai untuk a, b, c, d, e, f, g, h sehingga nilai x1 dan x2 dan x3 berbeda dengan jumlah yang besar. Lihat apakah Anda dapat melakukannya!
sumber
double d = double.MaxValue; Console.WriteLine(d + d - d - d); Console.WriteLine(d - d + d - d);
- outputnya adalah Infinity lalu 0.Ini sebenarnya mencakup lebih dari sekadar Java dan Javascript, dan kemungkinan akan memengaruhi bahasa pemrograman apa pun menggunakan floats atau doubles.
Dalam memori, floating point menggunakan format khusus sepanjang garis IEEE 754 (konverter memberikan penjelasan yang jauh lebih baik daripada yang saya bisa).
Ngomong-ngomong, ini konverter float.
http://www.h-schmidt.net/FloatConverter/
Hal tentang urutan operasi adalah "kehalusan" operasi.
Baris pertama Anda menghasilkan 29,41 dari dua nilai pertama, yang memberi kita 2 ^ 4 sebagai eksponen.
Baris kedua Anda menghasilkan 41,17 yang memberi kita 2 ^ 5 sebagai eksponen.
Kami kehilangan angka yang signifikan dengan meningkatkan eksponen, yang kemungkinan akan mengubah hasilnya.
Cobalah mencentang bit terakhir pada ujung kanan dan kiri untuk 41.17 dan Anda dapat melihat bahwa sesuatu sebagai "tidak signifikan" seperti 1/2 ^ 23 eksponen akan cukup untuk menyebabkan perbedaan floating point ini.
Sunting: Bagi Anda yang ingat angka signifikan, ini akan termasuk dalam kategori itu. 10 ^ 4 + 4999 dengan angka signifikan 1 akan menjadi 10 ^ 4. Dalam hal ini, angka signifikan jauh lebih kecil, tetapi kita dapat melihat hasilnya dengan 0,00000000004 yang melekat padanya.
sumber
Angka floating point direpresentasikan menggunakan format IEEE 754, yang menyediakan ukuran bit spesifik untuk mantissa (secara signifikan). Sayangnya ini memberi Anda sejumlah 'blok pembangun fraksional' untuk dimainkan, dan nilai fraksional tertentu tidak dapat direpresentasikan secara tepat.
Apa yang terjadi dalam kasus Anda adalah bahwa dalam kasus kedua, penambahan mungkin berjalan ke beberapa masalah presisi karena urutan penambahan dievaluasi. Saya belum menghitung nilainya, tetapi bisa jadi misalnya bahwa 23,53 + 17,64 tidak dapat diwakili secara tepat, sedangkan 23,53 + 5,88 bisa.
Sayangnya itu adalah masalah yang diketahui yang harus Anda tangani.
sumber
Saya percaya itu ada hubungannya dengan urutan evaulasi. Sementara jumlah secara alami sama di dunia matematika, di dunia biner bukannya A + B + C = D, itu
Jadi ada langkah sekunder di mana angka floating point bisa turun.
Ketika Anda mengubah urutan,
sumber