Apakah matematika floating point rusak?

2983

Pertimbangkan kode berikut:

0.1 + 0.2 == 0.3  ->  false
0.1 + 0.2         ->  0.30000000000000004

Mengapa ketidakakuratan ini terjadi?

Cato Johnston
sumber
127
Variabel floating point biasanya memiliki perilaku ini. Itu disebabkan oleh bagaimana mereka disimpan dalam perangkat keras. Untuk info lebih lanjut, lihat artikel Wikipedia tentang angka floating point .
Ben S
62
JavaScript memperlakukan desimal sebagai angka titik mengambang , yang berarti operasi seperti penambahan mungkin mengalami kesalahan pembulatan. Anda mungkin ingin melihat artikel ini: Apa Yang Harus Setiap Ilmuwan Komputer Tahu Tentang Aritmatika Floating-Point
matt b
4
Sekadar informasi, SEMUA tipe numerik dalam javascript adalah IEEE-754 Doubles.
Gary Willoughby
6
Karena JavaScript menggunakan standar IEEE 754 untuk Matematika, ia menggunakan angka mengambang 64-bit . Ini menyebabkan kesalahan presisi ketika melakukan perhitungan floating point (desimal), singkatnya, karena komputer bekerja di Basis 2 sedangkan desimal adalah Basis 10 .
Pardeep Jain

Jawaban:

2253

Biner floating point matematika seperti ini. Dalam sebagian besar bahasa pemrograman, ini didasarkan pada standar IEEE 754 . Inti masalahnya adalah bahwa angka-angka direpresentasikan dalam format ini sebagai bilangan bulat dikalikan dengan kekuatan dua; bilangan rasional (seperti 0.1, yang 1/10) yang penyebutnya bukan kekuatan dua tidak dapat secara tepat direpresentasikan.

Karena 0.1dalam binary64format standar , representasi dapat ditulis persis seperti

  • 0.1000000000000000055511151231257827021181583404541015625 dalam desimal, atau
  • 0x1.999999999999ap-4dalam notasi C99 hexfloat .

Sebaliknya, bilangan rasional 0.1, yaitu 1/10, dapat ditulis persis seperti

  • 0.1 dalam desimal, atau
  • 0x1.99999999999999...p-4dalam analog notasi hexfloat C99, di mana ...mewakili urutan tanpa akhir dari 9's.

Konstanta 0.2dan 0.3program Anda juga akan menjadi perkiraan nilai sebenarnya. Kebetulan yang terdekat doubledengan 0.2lebih besar dari bilangan rasional 0.2tetapi yang paling dekat doubledengan 0.3lebih kecil dari bilangan rasional 0.3. Jumlah 0.1dan 0.2akhirnya menjadi lebih besar dari angka rasional 0.3dan karenanya tidak setuju dengan konstanta dalam kode Anda.

Perlakuan yang cukup komprehensif dari masalah aritmatika floating-point adalah Apa Yang Harus Setiap Ilmuwan Komputer Tahu Tentang Aritmatika Floating-Point . Untuk penjelasan yang lebih mudah dicerna, lihat floating-point-gui.de .

Catatan Sisi: Semua sistem angka posisi (base-N) berbagi masalah ini dengan presisi

Angka desimal biasa (basis 10) memiliki masalah yang sama, itulah sebabnya angka seperti 1/3 berakhir sebagai 0,333333333 ...

Anda baru saja menemukan angka (3/10) yang kebetulan mudah direpresentasikan dengan sistem desimal, tetapi tidak cocok dengan sistem biner. Ini berlaku dua arah (sedikit banyak) juga: 1/16 adalah angka jelek dalam desimal (0,0625), tetapi dalam biner kelihatannya rapi seperti yang ke 10.000 dalam desimal (0,0001) ** - jika kita berada di kebiasaan menggunakan sistem nomor-2 basis dalam kehidupan sehari-hari kita, Anda bahkan akan melihat nomor itu dan secara naluriah memahami Anda bisa tiba di sana dengan membagi dua sesuatu, membagi dua itu, dan mengulanginya lagi, dan lagi dan lagi.

** Tentu saja, itu bukan bagaimana angka floating-point disimpan dalam memori (mereka menggunakan bentuk notasi ilmiah). Namun, ia mengilustrasikan poin bahwa kesalahan presisi floating-point biner cenderung muncul karena angka "dunia nyata" yang biasanya kita tertarik untuk bekerja adalah kekuatan sepuluh - tetapi hanya karena kita menggunakan sistem angka desimal hari- hari ini. Ini juga mengapa kita akan mengatakan hal-hal seperti 71% bukannya "5 dari setiap 7" (71% adalah perkiraan, karena 5/7 tidak dapat diwakili persis dengan angka desimal apa pun).

Jadi tidak: angka-angka floating point biner tidak rusak, mereka kebetulan tidak sempurna seperti setiap sistem nomor base-N lainnya :)

Catatan Sisi Samping: Bekerja dengan Mengapung dalam Pemrograman

Dalam praktiknya, masalah presisi ini berarti Anda harus menggunakan fungsi pembulatan untuk membulatkan angka floating point Anda ke sebanyak mungkin tempat desimal yang Anda minati sebelum Anda menampilkannya.

Anda juga perlu mengganti tes kesetaraan dengan perbandingan yang memungkinkan sejumlah toleransi, yang berarti:

Jangan tidak melakukanif (x == y) { ... }

Sebaliknya lakukan if (abs(x - y) < myToleranceValue) { ... }.

di mana absnilai absolut. myToleranceValueperlu dipilih untuk aplikasi khusus Anda - dan itu akan banyak berkaitan dengan berapa banyak "ruang gerak" yang Anda siapkan untuk memungkinkan, dan berapa jumlah terbesar yang akan Anda bandingkan (karena hilangnya masalah presisi) ). Waspadalah terhadap konstanta gaya "epsilon" dalam bahasa pilihan Anda. Ini tidak boleh digunakan sebagai nilai toleransi.

Daniel Scott
sumber
181
Saya pikir "beberapa kesalahan konstan" lebih tepat daripada "The Epsilon" karena tidak ada "The Epsilon" yang dapat digunakan dalam semua kasus. Epsilon yang berbeda perlu digunakan dalam situasi yang berbeda. Dan epsilon mesin hampir tidak pernah merupakan konstanta yang baik untuk digunakan.
Rotsor
34
Tidak sepenuhnya benar bahwa semua matematika floating-point didasarkan pada standar IEEE [754]. Masih ada beberapa sistem yang digunakan yang memiliki IBM hexadecimal FP lama, misalnya, dan masih ada kartu grafis yang tidak mendukung aritmetika IEEE-754. Memang benar untuk perkiraan yang wajar.
Stephen Canon
19
Cray membuang kepatuhan IEEE-754 untuk kecepatan. Java melonggarkan kepatuhannya sebagai optimisasi juga.
Seni Taylor
28
Saya pikir Anda harus menambahkan sesuatu ke jawaban ini tentang bagaimana perhitungan uang harus selalu, selalu dilakukan dengan aritmatika titik tetap pada bilangan bulat , karena uang dikuantisasi. (Mungkin masuk akal untuk melakukan perhitungan akuntansi internal dalam pecahan kecil satu sen, atau apa pun unit mata uang terkecil Anda - ini sering membantu misalnya mengurangi kesalahan pembulatan ketika mengkonversi "$ 29,99 per bulan" ke kurs harian - tetapi harus masih menjadi aritmatika titik tetap.)
zwol
18
Fakta menarik: 0,1 yang sangat tidak terwakili dalam titik-mengambang biner ini menyebabkan bug perangkat lunak rudal Patriot yang terkenal yang mengakibatkan 28 orang terbunuh dalam perang Irak pertama.
hdl
603

Perspektif Desainer Perangkat Keras

Saya percaya saya harus menambahkan perspektif perancang perangkat keras untuk ini karena saya merancang dan membangun perangkat keras floating point. Mengetahui asal kesalahan dapat membantu dalam memahami apa yang terjadi dalam perangkat lunak, dan pada akhirnya, saya harap ini membantu menjelaskan alasan mengapa kesalahan floating point terjadi dan tampaknya menumpuk dari waktu ke waktu.

1. Ikhtisar

Dari perspektif teknik, sebagian besar operasi floating point akan memiliki beberapa elemen kesalahan karena perangkat keras yang melakukan perhitungan floating point hanya diperlukan memiliki kesalahan kurang dari setengah dari satu unit di tempat terakhir. Oleh karena itu, banyak perangkat keras akan berhenti pada ketelitian yang hanya diperlukan untuk menghasilkan kesalahan kurang dari setengah dari satu unit di tempat terakhir untuk operasi tunggal yang sangat bermasalah dalam divisi floating point. Apa yang merupakan operasi tunggal tergantung pada berapa banyak operan yang diambil unit. Bagi sebagian besar, itu dua, tetapi beberapa unit mengambil 3 atau lebih operan. Karena itu, tidak ada jaminan bahwa operasi yang berulang akan menghasilkan kesalahan yang diinginkan karena kesalahan bertambah seiring waktu.

2. Standar

Sebagian besar prosesor mengikuti standar IEEE-754 tetapi beberapa menggunakan standar denormalized, atau berbeda. Sebagai contoh, ada mode denormalized di IEEE-754 yang memungkinkan representasi angka floating point yang sangat kecil dengan mengorbankan presisi. Namun, yang berikut ini akan mencakup mode normal IEEE-754 yang merupakan mode operasi khas.

Dalam standar IEEE-754, perancang perangkat keras diperbolehkan nilai kesalahan / epsilon selama itu kurang dari setengah dari satu unit di tempat terakhir, dan hasilnya hanya harus kurang dari setengah dari satu unit di yang terakhir tempat untuk satu operasi. Ini menjelaskan mengapa ketika ada operasi berulang, kesalahan bertambah. Untuk presisi ganda IEEE-754, ini adalah bit ke-54, karena 53 bit digunakan untuk mewakili bagian numerik (dinormalisasi), juga disebut mantissa, dari angka floating point (misalnya 5.3 dalam 5.3e5). Bagian selanjutnya membahas lebih rinci tentang penyebab kesalahan perangkat keras pada berbagai operasi floating point.

3. Penyebab Kesalahan Pembulatan di Divisi

Penyebab utama kesalahan dalam pembagian floating point adalah algoritma pembagian yang digunakan untuk menghitung hasil bagi. Sebagian besar sistem komputer menghitung pembagian menggunakan perkalian dengan invers, terutama dalam Z=X/Y,Z = X * (1/Y). Pembagian dihitung secara iteratif yaitu setiap siklus menghitung beberapa bit hasil bagi sampai presisi yang diinginkan tercapai, yang untuk IEEE-754 adalah apa saja dengan kesalahan kurang dari satu unit di tempat terakhir. Tabel kebalikan dari Y (1 / Y) dikenal sebagai tabel pemilihan hasil bagi (QST) dalam pembagian yang lambat, dan ukuran dalam bit dari tabel pemilihan hasil bagi biasanya adalah lebar radix, atau sejumlah bit dari hasil bagi yang dihitung dalam setiap iterasi, ditambah beberapa bit penjaga. Untuk standar IEEE-754, presisi ganda (64-bit), itu akan menjadi ukuran radix pembagi, ditambah beberapa bit penjaga k, di mana k>=2. Jadi misalnya, Tabel Pemilihan Quotient khas untuk pembagi yang menghitung 2 bit hasil bagi pada suatu waktu (radix 4) akan menjadi 2+2= 4bit (ditambah beberapa bit opsional).

3.1 Kesalahan Pembulatan Divisi: Perkiraan Timbal Balik

Apa yang ada dalam tabel pemilihan hasil bagi tergantung pada metode pembagian : divisi lambat seperti divisi SRT, atau divisi cepat seperti divisi Goldschmidt; setiap entri dimodifikasi sesuai dengan algoritma divisi dalam upaya untuk menghasilkan kesalahan serendah mungkin. Bagaimanapun, dalam semua kasus, semua timbal balik adalah perkiraandari timbal balik yang sebenarnya dan memperkenalkan beberapa elemen kesalahan. Metode pembagian lambat dan pembagian cepat menghitung hasil bagi secara iteratif, yaitu beberapa jumlah bit hasil bagi dihitung setiap langkah, kemudian hasilnya dikurangi dari dividen, dan pembagi mengulangi langkah-langkah sampai kesalahan kurang dari setengah dari satu unit di tempat terakhir. Metode pembagian lambat menghitung jumlah digit hasil bagi pada setiap langkah dan biasanya lebih murah untuk dibangun, dan metode pembagian cepat menghitung jumlah variabel digit per langkah dan biasanya lebih mahal untuk dibangun. Bagian terpenting dari metode pembagian adalah bahwa kebanyakan dari mereka bergantung pada perkalian berulang dengan perkiraan timbal balik, sehingga mereka rentan terhadap kesalahan.

4. Kesalahan Pembulatan dalam Operasi Lain: Pemotongan

Penyebab lain dari kesalahan pembulatan dalam semua operasi adalah mode pemotongan yang berbeda dari jawaban akhir yang dimungkinkan oleh IEEE-754. Ada truncate, bulat-ke-nol, bulat-ke-terdekat (default), bulat-bawah, dan bulat-atas. Semua metode memperkenalkan elemen kesalahan kurang dari satu unit di tempat terakhir untuk satu operasi. Seiring waktu dan operasi berulang, pemotongan juga menambah kumulatif untuk kesalahan yang dihasilkan. Kesalahan pemotongan ini sangat bermasalah dalam eksponensial, yang melibatkan beberapa bentuk perkalian berulang.

5. Operasi Berulang

Karena perangkat keras yang melakukan perhitungan floating point hanya perlu menghasilkan hasil dengan kesalahan kurang dari setengah dari satu unit di tempat terakhir untuk satu operasi, kesalahan akan tumbuh selama operasi berulang jika tidak ditonton. Ini adalah alasan bahwa dalam perhitungan yang memerlukan kesalahan terikat, ahli matematika menggunakan metode seperti menggunakan digit genap bulat ke terdekat di tempat terakhir IEEE-754, karena, seiring waktu, kesalahan lebih cenderung untuk saling membatalkan keluar, dan Interval Aritmatika dikombinasikan dengan variasi mode pembulatan IEEE 754untuk memprediksi kesalahan pembulatan, dan memperbaikinya. Karena kesalahan relatifnya yang rendah dibandingkan dengan mode pembulatan lainnya, pembulatan ke digit genap terdekat (di tempat terakhir), adalah mode pembulatan default dari IEEE-754.

Perhatikan bahwa mode pembulatan default, angka genap bulat ke terdekat di tempat terakhir , menjamin kesalahan kurang dari setengah dari satu unit di tempat terakhir untuk satu operasi. Menggunakan pemotongan, pembulatan ke atas, dan pembulatan ke bawah saja dapat menyebabkan kesalahan yang lebih besar dari setengah dari satu unit di tempat terakhir, tetapi kurang dari satu unit di tempat terakhir, sehingga mode ini tidak direkomendasikan kecuali mereka digunakan dalam Aritmatika Interval.

6. Ringkasan

Singkatnya, alasan mendasar untuk kesalahan dalam operasi floating point adalah kombinasi dari pemotongan dalam perangkat keras, dan pemotongan dari suatu timbal balik dalam kasus pembagian. Karena standar IEEE-754 hanya membutuhkan kesalahan kurang dari setengah dari satu unit di tempat terakhir untuk satu operasi, kesalahan floating point atas operasi yang berulang akan bertambah kecuali terkoreksi.

KernelPanik
sumber
8
(3) salah. Kesalahan pembulatan dalam divisi tidak kurang dari satu unit di tempat terakhir, tetapi paling banyak setengah unit di tempat terakhir.
gnasher729
6
@ gnasher729 Tangkapan yang bagus. Sebagian besar operasi dasar juga memiliki kesalahan kurang dari ½ dari satu unit di tempat terakhir menggunakan mode pembulatan IEEE default. Mengedit penjelasan, dan juga mencatat bahwa kesalahan mungkin lebih besar dari 1/2 dari satu ulp tetapi kurang dari 1 ulp jika pengguna menimpa mode pembulatan default (ini terutama berlaku di embedded system).
KernelPanik
39
(1) Nomor titik apung tidak memiliki kesalahan. Setiap nilai floating point persis seperti apa adanya. Sebagian besar (tetapi tidak semua) operasi floating point memberikan hasil yang tidak tepat. Misalnya, tidak ada nilai titik apung biner yang persis sama dengan 1.0 / 10.0. Beberapa operasi (misalnya, 1.0 + 1.0) memang memberikan hasil yang tepat di sisi lain.
Solomon Slow
19
"Penyebab utama kesalahan dalam pembagian floating point, adalah algoritma pembagian yang digunakan untuk menghitung hasil bagi" adalah hal yang sangat menyesatkan untuk dikatakan. Untuk divisi penyesuaian IEEE-754, satu - satunya penyebab kesalahan dalam divisi floating-point adalah ketidakmampuan hasil untuk diwakili secara tepat dalam format hasil; hasil yang sama dihitung terlepas dari algoritma yang digunakan.
Stephen Canon
6
@ Matt Maaf atas tanggapan yang terlambat. Ini pada dasarnya karena masalah sumber daya / waktu dan pengorbanan. Ada cara untuk melakukan pembagian panjang / divisi lebih 'normal', itu disebut Divisi SRT dengan radix dua. Namun, ini berulang kali menggeser dan mengurangi pembagi dari dividen dan membutuhkan banyak siklus clock karena hanya menghitung satu bit dari hasil bagi per siklus clock. Kami menggunakan tabel resiprokal sehingga kami dapat menghitung lebih banyak bit hasil bagi per siklus dan membuat pengorbanan kinerja / kecepatan yang efektif.
KernelPanik
463

Ketika Anda mengonversi .1 atau 1/10 ke basis 2 (biner) Anda mendapatkan pola berulang setelah titik desimal, sama seperti mencoba untuk mewakili 1/3 di basis 10. Nilainya tidak tepat, dan oleh karena itu Anda tidak dapat melakukan matematika persis dengan itu menggunakan metode floating point normal.

Joel Coehoorn
sumber
133
Jawaban yang bagus dan singkat. Pola berulang terlihat seperti 0,00011001100110011001100110011001100110011001100110011 ...
Konstantin Chernov
4
Ini tidak menjelaskan mengapa bukan algoritma yang lebih baik digunakan yang tidak mengkonversi menjadi binari.
Dmitri Zaitsev
12
Karena kinerja. Menggunakan biner beberapa ribu kali lebih cepat, karena ini asli untuk mesin.
Joel Coehoorn
7
ADA metode yang menghasilkan nilai desimal tepat. BCD (Biner berkode desimal) atau berbagai bentuk angka desimal lainnya. Namun, keduanya lebih lambat (LOT lebih lambat) dan mengambil lebih banyak penyimpanan daripada menggunakan floating point biner. (sebagai contoh, BCD yang dikemas menyimpan 2 angka desimal dalam satu byte. Itu 100 nilai yang mungkin dalam byte yang sebenarnya dapat menyimpan 256 nilai yang mungkin, atau 100/256, yang menghabiskan sekitar 60% dari nilai yang mungkin dari sebuah byte.)
Duncan C
16
@ Jacksonkr Anda masih berpikir di base-10. Komputer adalah basis-2.
Joel Coehoorn
307

Sebagian besar jawaban di sini menjawab pertanyaan ini dengan istilah yang sangat kering dan teknis. Saya ingin membahas hal ini dalam istilah yang manusia normal dapat mengerti.

Bayangkan Anda mencoba mengiris pizza. Anda memiliki pemotong pizza robot yang dapat memotong irisan pizza persis dua. Itu bisa membagi dua pizza utuh, atau bisa membagi dua irisan yang ada, tetapi dalam hal apapun, setengahnya selalu tepat.

Pemotong pizza itu memiliki gerakan yang sangat baik, dan jika Anda mulai dengan pizza utuh, lalu membagi dua itu, dan terus membagi dua irisan terkecil setiap kali, Anda dapat melakukan separuh hingga 53 kali sebelum irisan terlalu kecil bahkan untuk kemampuan presisi tinggi. . Pada titik itu, Anda tidak lagi dapat membagi dua irisan yang sangat tipis itu, tetapi harus memasukkan atau mengeluarkannya apa adanya.

Sekarang, bagaimana Anda memotong semua irisan sedemikian rupa sehingga akan menambahkan hingga sepersepuluh (0,1) atau seperlima (0,2) pizza? Benar-benar memikirkannya, dan cobalah mengatasinya. Anda bahkan dapat mencoba menggunakan pizza sungguhan, jika Anda memiliki pemotong pizza presisi mitis di tangan. :-)


Kebanyakan programmer berpengalaman, tentu saja, tahu jawaban sebenarnya, yaitu bahwa tidak ada cara untuk menyatukan kepingan tepat sepersepuluh atau seperlima dari pizza menggunakan mereka iris, tidak peduli seberapa halus Anda mengiris mereka. Anda dapat melakukan perkiraan yang cukup baik, dan jika Anda menambahkan perkiraan 0,1 dengan perkiraan 0,2, Anda mendapatkan perkiraan yang cukup baik 0,3, tetapi masih saja itu, perkiraan.

Untuk angka presisi ganda (yang merupakan presisi yang memungkinkan Anda membagi dua pizza Anda sebanyak 53 kali), angka yang segera berkurang dan lebih besar dari 0,1 adalah 0,09999999999999999167332731531132594682276248931884765625 dan 0.100000000000000000000550011151231257827021181583404510 Yang terakhir ini sedikit lebih dekat ke 0,1 dari yang sebelumnya, jadi parser numerik akan, diberi input 0,1, mendukung yang terakhir.

(Perbedaan antara kedua angka itu adalah "irisan terkecil" yang harus kita putuskan untuk dimasukkan, yang menimbulkan bias ke atas, atau mengecualikan, yang menghasilkan bias ke bawah. Istilah teknis untuk irisan terkecil adalah ulp .)

Dalam kasus 0,2, angkanya semuanya sama, hanya ditingkatkan dengan faktor 2. Sekali lagi, kami menyukai nilai yang sedikit lebih tinggi dari 0,2.

Perhatikan bahwa dalam kedua kasus, perkiraan untuk 0,1 dan 0,2 memiliki sedikit bias ke atas. Jika kita menambahkan cukup bias ini, mereka akan mendorong angka lebih jauh dan lebih jauh dari apa yang kita inginkan, dan pada kenyataannya, dalam kasus 0,1 + 0,2, biasnya cukup tinggi sehingga jumlah yang dihasilkan tidak lagi angka terdekat ke 0,3.

Secara khusus, 0,1 + 0,2 benar-benar 0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 = 0.3000000000000000444089209850062616169452667236328125, sedangkan jumlah yang paling dekat dengan 0,3 sebenarnya 0,299999999999999988897769753748434595763683319091796875.


PS Beberapa bahasa pemrograman juga menyediakan pemotong pizza yang dapat membagi irisan menjadi persepuluh yang tepat . Meskipun pemotong pizza seperti itu tidak umum, jika Anda memiliki akses ke salah satunya, Anda harus menggunakannya ketika penting untuk bisa mendapatkan sepersepuluh atau seperlima dari sepotong.

(Awalnya diposting di Quora.)

Chris Jester-Young
sumber
3
Perhatikan bahwa ada beberapa bahasa yang menyertakan matematika pasti. Salah satu contohnya adalah Skema, misalnya melalui GNU Guile. Lihat draketo.de/english/exact-math-to-thecue - ini menjaga matematika sebagai pecahan dan hanya mengiris pada akhirnya.
Arne Babenhauserheide
5
@ FloatingRock Sebenarnya, sangat sedikit bahasa pemrograman umum yang memiliki bilangan rasional. Arne adalah seorang Schemer, seperti saya, jadi ini adalah hal-hal yang kita manja.
Chris Jester-Young
5
@ArneBabenhauserheide Saya pikir perlu menambahkan bahwa ini hanya akan bekerja dengan bilangan rasional. Jadi, jika Anda melakukan matematika dengan angka irasional seperti pi, Anda harus menyimpannya sebagai kelipatan pi. Tentu saja, setiap penghitungan yang melibatkan pi tidak dapat direpresentasikan sebagai angka desimal yang tepat.
Aidiakapi
13
@connexo Oke. Bagaimana Anda memprogram rotator pizza Anda untuk mendapatkan 36 derajat? Apa itu 36 derajat? (Petunjuk: jika Anda dapat mendefinisikan ini dengan cara yang tepat, Anda juga memiliki pemotong pizza irisan-an-tepat-kesepuluh.) Dengan kata lain, Anda tidak dapat benar-benar memiliki 1/360 (gelar) atau 1 / 10 (36 derajat) dengan hanya floating point biner.
Chris Jester-Young
12
@connexo Juga, "setiap idiot" tidak dapat memutar pizza tepat 36 derajat. Manusia terlalu rentan untuk melakukan sesuatu yang sangat tepat.
Chris Jester-Young
212

Kesalahan pembulatan titik mengambang. 0,1 tidak dapat direpresentasikan secara akurat dalam basis-2 seperti pada basis-10 karena faktor prima yang hilang dari 5. Sama seperti 1/3 mengambil jumlah digit tak terbatas untuk mewakili dalam desimal, tetapi "0,1" pada basis-3, 0,1 mengambil jumlah digit tak terbatas di basis-2 di mana ia tidak di basis-10. Dan komputer tidak memiliki jumlah memori yang tak terbatas.

Devin Jeanpierre
sumber
133
komputer tidak memerlukan jumlah memori tak terbatas untuk mendapatkan 0,1 + 0,2 = 0,3 benar
Pacerier
23
@Pacerier Tentu, mereka bisa menggunakan dua bilangan bulat presisi tanpa batas untuk mewakili sebagian kecil, atau mereka bisa menggunakan notasi kutipan. Ini adalah gagasan spesifik "biner" atau "desimal" yang membuat ini tidak mungkin - gagasan bahwa Anda memiliki urutan angka biner / desimal dan, di suatu tempat di sana, titik radix. Untuk mendapatkan hasil rasional yang tepat, kami membutuhkan format yang lebih baik.
Devin Jeanpierre
15
@Pacerier: Baik floating-point biner maupun desimal tidak dapat dengan tepat menyimpan 1/3 atau 1/13. Tipe floating-point desimal dapat dengan tepat mewakili nilai-nilai dari bentuk M / 10 ^ E, tetapi kurang tepat daripada angka-angka floating-point biner berukuran sama ketika datang untuk mewakili sebagian besar fraksi lainnya . Dalam banyak aplikasi, lebih berguna untuk memiliki presisi yang lebih tinggi dengan fraksi yang sewenang-wenang daripada memiliki presisi sempurna dengan beberapa yang "khusus".
supercat
13
@ Peracer Mereka melakukannya jika mereka menyimpan angka sebagai pelampung biner, yang merupakan inti dari jawabannya.
Mark Amery
3
@ chux: Perbedaan dalam presisi antara tipe biner dan desimal tidak besar, tetapi perbedaan 10: 1 dalam kasus terbaik vs presisi terburuk untuk tipe desimal jauh lebih besar daripada perbedaan 2: 1 dengan tipe biner. Saya ingin tahu apakah ada orang yang membangun perangkat keras atau perangkat lunak tertulis untuk beroperasi secara efisien pada salah satu dari jenis desimal, karena tidak ada yang mau menerima implementasi yang efisien dalam perangkat keras maupun perangkat lunak.
supercat
121

Selain jawaban yang benar lainnya, Anda mungkin ingin mempertimbangkan penskalaan nilai Anda untuk menghindari masalah dengan aritmatika floating-point.

Sebagai contoh:

var result = 1.0 + 2.0;     // result === 3.0 returns true

... dari pada:

var result = 0.1 + 0.2;     // result === 0.3 returns false

Ekspresi 0.1 + 0.2 === 0.3kembali falsedalam JavaScript, tapi untungnya aritmatika integer dalam floating-point tepat, sehingga kesalahan representasi desimal dapat dihindari dengan penskalaan.

Sebagai contoh praktis, untuk menghindari masalah floating-point di mana akurasi adalah yang terpenting, disarankan 1 untuk menangani uang sebagai bilangan bulat yang mewakili jumlah sen: 2550sen, bukan 25.50dolar.


1 Douglas Crockford: JavaScript: Bagian Yang Baik : Lampiran A - Bagian yang Mengerikan (halaman 105) .

Daniel Vassallo
sumber
3
Masalahnya adalah bahwa konversi itu sendiri tidak akurat. 16.08 * 100 = 1607.9999999999998. Apakah kita harus menggunakan pemisahan nomor dan mengkonversi secara terpisah (seperti dalam 16 * 100 + 08 = 1608)?
Jason
38
Solusinya di sini adalah melakukan semua perhitungan Anda dalam bilangan bulat kemudian bagi dengan proporsi Anda (100 dalam kasus ini) dan bulatkan hanya ketika menyajikan data. Itu akan memastikan bahwa perhitungan Anda akan selalu tepat.
David Granado
16
Hanya sedikit nitpick: bilangan bulat aritmatika hanya tepat di floating-point ke titik (pun intended). Jika angkanya lebih besar dari 0x1p53 (untuk menggunakan notasi floating point heksadesimal Java 7, = 9007199254740992), maka ulp adalah 2 pada titik itu dan 0x1p53 + 1 dibulatkan ke 0x1p53 (dan 0x1p53 + 3 dibulatkan menjadi 0x1p53 + 4, karena round-to-even). :-D Tapi yang pasti, jika nomor Anda lebih kecil dari 9 kuadriliun, Anda harus baik-baik saja. :-P
Chris Jester-Young
2
Jason, Anda harus menyelesaikan hasilnya (int) (16.08 * 100 + 0.5)
Mikhail Semenov
@CodyBugstein " Jadi bagaimana Anda mendapatkan .1 + .2 untuk menunjukkan .3? " Tulis fungsi cetak khusus untuk menempatkan desimal di tempat yang Anda inginkan.
RonJohn
113

Jawaban saya cukup panjang, jadi saya membaginya menjadi tiga bagian. Karena pertanyaannya adalah tentang floating point matematika, saya telah menekankan apa yang sebenarnya dilakukan mesin. Saya juga membuatnya spesifik untuk menggandakan (64 bit) presisi, tetapi argumennya berlaku sama untuk setiap aritmatika floating point.

Pembukaan

Nomor format biner floating-point (binary64) IEEE 754 presisi ganda mewakili sejumlah formulir

nilai = (-1) ^ s * (1.m 51 m 50 ... m 2 m 1 m 0 ) 2 * 2 e-1023

dalam 64 bit:

  • Bit pertama adalah bit tanda : 1jika angkanya negatif, 0jika tidak 1 .
  • 11 bit berikutnya adalah eksponen , yang diimbangi oleh 1023. Dengan kata lain, setelah membaca bit eksponen dari angka presisi ganda, 1023 harus dikurangkan untuk mendapatkan kekuatan dua.
  • 52 bit sisanya adalah yang signifikan (atau mantra). Dalam mantissa, 'tersirat' 1.selalu 2 diabaikan karena bit paling signifikan dari nilai biner apa pun adalah 1.

1 - IEEE 754 memungkinkan untuk konsep nol yang ditandatangani - +0dan -0diperlakukan secara berbeda: 1 / (+0)infinity positif; 1 / (-0)adalah infinity negatif. Untuk nilai nol, bit mantissa dan eksponen semuanya nol. Catatan: nilai nol (+0 dan -0) secara eksplisit tidak diklasifikasikan sebagai denormal 2 .

2 - Ini bukan kasus untuk angka-angka denormal , yang memiliki eksponen offset nol (dan tersirat 0.). Kisaran angka presisi ganda tidak normal adalah d min ≤ | x | ≤ d max , di mana d min (terkecil representable nomor nol) adalah 2 -1.023-51 (≈ 4,94 * 10 -324 ) dan d max (jumlah denormal terbesar, yang mantissa seluruhnya terdiri dari 1s) adalah 2 -1023 + 1 - 2 -1023 - 51 (≈ 2.225 * 10 -308 ).


Mengubah angka presisi ganda menjadi biner

Banyak konverter online ada untuk mengubah angka floating point presisi ganda menjadi biner (misalnya di binaryconvert.com ), tetapi di sini ada beberapa contoh kode C # untuk mendapatkan representasi IEEE 754 untuk angka presisi ganda (saya memisahkan tiga bagian dengan titik dua ( :) :

public static string BinaryRepresentation(double value)
{
    long valueInLongType = BitConverter.DoubleToInt64Bits(value);
    string bits = Convert.ToString(valueInLongType, 2);
    string leadingZeros = new string('0', 64 - bits.Length);
    string binaryRepresentation = leadingZeros + bits;

    string sign = binaryRepresentation[0].ToString();
    string exponent = binaryRepresentation.Substring(1, 11);
    string mantissa = binaryRepresentation.Substring(12);

    return string.Format("{0}:{1}:{2}", sign, exponent, mantissa);
}

Sampai ke titik: pertanyaan awal

(Lewati ke bawah untuk versi TL; DR)

Cato Johnston (penanya pertanyaan) bertanya mengapa 0,1 + 0,2! = 0,3.

Ditulis dalam biner (dengan titik dua memisahkan tiga bagian), representasi IEEE 754 dari nilai-nilai tersebut adalah:

0.1 => 0:01111111011:1001100110011001100110011001100110011001100110011010
0.2 => 0:01111111100:1001100110011001100110011001100110011001100110011010

Perhatikan bahwa mantissa terdiri dari angka berulang 0011. Ini adalah kunci mengapa ada kesalahan pada perhitungan - 0,1, 0,2 dan 0,3 tidak dapat diwakili dalam biner tepatnya dalam jumlah bit biner yang terbatas lebih dari 1/9, 1/3 atau 1/7 dapat diwakili secara tepat dalam angka desimal .

Perhatikan juga bahwa kita dapat mengurangi daya dalam eksponen sebanyak 52 dan menggeser titik dalam representasi biner ke kanan sebanyak 52 tempat (seperti 10 -3 * 1.23 == 10 -5 * 123). Ini kemudian memungkinkan kita untuk mewakili representasi biner sebagai nilai tepat yang diwakilinya dalam bentuk a * 2 p . di mana 'a' adalah bilangan bulat.

Mengubah eksponen menjadi desimal, menghapus offset, dan menambahkan kembali yang tersirat 1(dalam kurung siku), 0,1 dan 0,2 adalah:

0.1 => 2^-4 * [1].1001100110011001100110011001100110011001100110011010
0.2 => 2^-3 * [1].1001100110011001100110011001100110011001100110011010
or
0.1 => 2^-56 * 7205759403792794 = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794 = 0.200000000000000011102230246251565404236316680908203125

Untuk menambahkan dua angka, eksponen harus sama, yaitu:

0.1 => 2^-3 *  0.1100110011001100110011001100110011001100110011001101(0)
0.2 => 2^-3 *  1.1001100110011001100110011001100110011001100110011010
sum =  2^-3 * 10.0110011001100110011001100110011001100110011001100111
or
0.1 => 2^-55 * 3602879701896397  = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794  = 0.200000000000000011102230246251565404236316680908203125
sum =  2^-55 * 10808639105689191 = 0.3000000000000000166533453693773481063544750213623046875

Karena jumlahnya bukan dari bentuk 2 n * 1. {bbb} kita menambah eksponen dengan satu dan menggeser titik desimal ( biner ) untuk mendapatkan:

sum = 2^-2  * 1.0011001100110011001100110011001100110011001100110011(1)
    = 2^-54 * 5404319552844595.5 = 0.3000000000000000166533453693773481063544750213623046875

Sekarang ada 53 bit dalam mantissa (yang ke-53 adalah dalam tanda kurung di baris di atas). Mode pembulatan default untuk IEEE 754 adalah ' Round to Nearest ' - yaitu jika angka x jatuh antara dua nilai a dan b , nilai di mana bit paling signifikan adalah nol dipilih.

a = 2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875
  = 2^-2  * 1.0011001100110011001100110011001100110011001100110011

x = 2^-2  * 1.0011001100110011001100110011001100110011001100110011(1)

b = 2^-2  * 1.0011001100110011001100110011001100110011001100110100
  = 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125

Perhatikan bahwa a dan b hanya berbeda pada bit terakhir; ...0011+ 1= ...0100. Dalam hal ini, nilai dengan bit nol paling signifikan adalah b , jadi jumlahnya adalah:

sum = 2^-2  * 1.0011001100110011001100110011001100110011001100110100
    = 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125

sedangkan representasi biner 0,3 adalah:

0.3 => 2^-2  * 1.0011001100110011001100110011001100110011001100110011
    =  2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875

yang hanya berbeda dari representasi biner dari jumlah 0,1 dan 0,2 dengan 2 -54 .

Representasi biner 0,1 dan 0,2 adalah representasi paling akurat dari angka-angka yang diizinkan oleh IEEE 754. Penambahan representasi ini, karena mode pembulatan default, menghasilkan nilai yang berbeda hanya dalam bit-paling-signifikan.

TL; DR

Menulis 0.1 + 0.2dalam representasi biner IEEE 754 (dengan titik dua memisahkan tiga bagian) dan membandingkannya dengan 0.3ini, ini adalah (Saya telah memasukkan bit yang berbeda dalam tanda kurung siku):

0.1 + 0.2 => 0:01111111101:0011001100110011001100110011001100110011001100110[100]
0.3       => 0:01111111101:0011001100110011001100110011001100110011001100110[011]

Dikonversi kembali ke desimal, nilai-nilai ini adalah:

0.1 + 0.2 => 0.300000000000000044408920985006...
0.3       => 0.299999999999999988897769753748...

Perbedaannya persis 2 -54 , yaitu ~ 5.5511151231258 × 10 -17 - tidak signifikan (untuk banyak aplikasi) jika dibandingkan dengan nilai aslinya.

Membandingkan beberapa bit terakhir dari angka floating point secara inheren berbahaya, karena siapa pun yang membaca " Apa Yang Harus Diketahui Setiap Ilmuwan Komputer Tentang Aritmatika Titik Apung " (yang mencakup semua bagian utama dari jawaban ini) akan tahu.

Sebagian besar kalkulator menggunakan digit penjaga tambahan untuk mengatasi masalah ini, yang adalah bagaimana 0.1 + 0.2memberi 0.3: beberapa bit terakhir dibulatkan.

Wai Ha Lee
sumber
14
Jawaban saya ditolak sesaat setelah mempostingnya. Sejak itu saya telah membuat banyak perubahan (termasuk secara eksplisit mencatat bit berulang ketika menulis 0,1 dan 0,2 dalam biner, yang saya hilangkan dalam aslinya). Jika pemilih bawah melihat ini, bisakah Anda memberi saya umpan balik sehingga saya dapat meningkatkan jawaban saya? Saya merasa bahwa jawaban saya menambahkan sesuatu yang baru karena perlakuan jumlah dalam IEEE 754 tidak tercakup dengan cara yang sama dalam jawaban lain. Sementara "Apa yang harus diketahui oleh setiap ilmuwan komputer ..." mencakup beberapa materi yang sama, jawaban saya berkaitan secara khusus dengan kasus 0,1 + 0,2.
Wai Ha Lee
57

Nomor titik apung yang disimpan di komputer terdiri dari dua bagian, bilangan bulat dan eksponen yang dibawa ke dasar dan dikalikan dengan bagian bilangan bulat.

Jika komputer bekerja di basis 10, 0.1akan 1 x 10⁻¹, 0.2akan 2 x 10⁻¹, dan 0.3akan 3 x 10⁻¹. Matematika bilangan bulat mudah dan tepat, jadi menambahkan 0.1 + 0.2jelas akan menghasilkan 0.3.

Komputer biasanya tidak bekerja di basis 10, mereka bekerja di basis 2. Anda masih bisa mendapatkan hasil yang tepat untuk beberapa nilai, misalnya 0.5ada 1 x 2⁻¹dan 0.25sedang 1 x 2⁻², dan menambahkannya 3 x 2⁻², atau 0.75. Persis.

Masalahnya datang dengan angka-angka yang dapat diwakili tepat di basis 10, tetapi tidak di basis 2. Angka-angka itu harus dibulatkan ke persamaan terdekatnya. Dengan asumsi format floating point IEEE 64-bit yang sangat umum, nomor terdekat 0.1adalah 3602879701896397 x 2⁻⁵⁵, dan nomor terdekat 0.2adalah 7205759403792794 x 2⁻⁵⁵; menambahkannya bersama-sama menghasilkan 10808639105689191 x 2⁻⁵⁵, atau nilai desimal tepat dari 0.3000000000000000444089209850062616169452667236328125. Nomor titik apung umumnya dibulatkan untuk ditampilkan.

Mark tebusan
sumber
2
@ Mark Terima kasih atas penjelasan yang jelas ini tetapi kemudian muncul pertanyaan mengapa 0,1 + 0,4 persis menambahkan hingga 0,5 (minimal dengan Python 3). Juga apa cara terbaik untuk memeriksa kesetaraan saat menggunakan pelampung di Python 3?
pchegoor
2
@ user2417881 Operasi floating point IEEE memiliki aturan pembulatan untuk setiap operasi, dan terkadang pembulatan dapat menghasilkan jawaban yang tepat bahkan ketika kedua angka dimatikan sedikit. Detailnya terlalu panjang untuk dikomentari dan saya bukan ahli di dalamnya. Seperti yang Anda lihat dalam jawaban ini 0,5 adalah salah satu dari beberapa desimal yang dapat direpresentasikan dalam biner, tapi itu hanya kebetulan. Untuk pengujian kesetaraan, lihat stackoverflow.com/questions/5595425/… .
Mark Ransom
1
@ user2417881 pertanyaan Anda menggelitik saya, jadi saya mengubahnya menjadi pertanyaan dan jawaban lengkap: stackoverflow.com/q/48374522/5987
Mark Ransom
47

Kesalahan pembulatan titik mengambang. Dari Apa Yang Harus Diketahui Setiap Ilmuwan Tentang Aritmatika Titik Apung :

Meremas bilangan real tak terhingga ke dalam jumlah bit terbatas membutuhkan representasi perkiraan. Meskipun ada banyak bilangan bulat yang tak terhingga, dalam sebagian besar program hasil perhitungan bilangan bulat dapat disimpan dalam 32 bit. Sebaliknya, mengingat jumlah bit tetap apa pun, sebagian besar perhitungan dengan bilangan real akan menghasilkan jumlah yang tidak dapat direpresentasikan secara tepat menggunakan banyak bit tersebut. Oleh karena itu, hasil dari perhitungan titik-mengambang harus sering dibulatkan agar sesuai dengan representasi terbatasnya. Kesalahan pembulatan ini adalah fitur karakteristik perhitungan floating-point.

Brett Daniel
sumber
33

Solusi saya:

function add(a, b, precision) {
    var x = Math.pow(10, precision || 2);
    return (Math.round(a * x) + Math.round(b * x)) / x;
}

presisi mengacu pada jumlah digit yang ingin Anda pertahankan setelah titik desimal selama penambahan.

Justineo
sumber
30

Banyak jawaban bagus telah diposting, tetapi saya ingin menambahkan satu lagi.

Tidak semua angka dapat direpresentasikan melalui float / doubles Sebagai contoh, angka "0,2" akan direpresentasikan sebagai "0,200000003" dalam presisi tunggal dalam standar IEEE754 float point.

Model untuk menyimpan bilangan real di bawah kap mewakili angka float sebagai

masukkan deskripsi gambar di sini

Meskipun Anda dapat mengetik 0.2dengan mudah, FLT_RADIXdan DBL_RADIX2; bukan 10 untuk komputer dengan FPU yang menggunakan "Standar IEEE untuk Bith Floating-Point Arithmetic (ISO / IEEE Std 754-1985)".

Jadi agak sulit untuk menggambarkan angka-angka itu dengan tepat. Bahkan jika Anda menentukan variabel ini secara eksplisit tanpa perhitungan perantara.

bruziuz
sumber
28

Beberapa statistik terkait dengan pertanyaan presisi ganda yang terkenal ini.

Saat menambahkan semua nilai ( a + b ) menggunakan langkah 0,1 (dari 0,1 hingga 100) kami memiliki ~ 15% kemungkinan kesalahan presisi . Perhatikan bahwa kesalahan dapat menghasilkan nilai yang sedikit lebih besar atau lebih kecil. Berikut ini beberapa contohnya:

0.1 + 0.2 = 0.30000000000000004 (BIGGER)
0.1 + 0.7 = 0.7999999999999999 (SMALLER)
...
1.7 + 1.9 = 3.5999999999999996 (SMALLER)
1.7 + 2.2 = 3.9000000000000004 (BIGGER)
...
3.2 + 3.6 = 6.800000000000001 (BIGGER)
3.2 + 4.4 = 7.6000000000000005 (BIGGER)

Ketika mengurangi semua nilai ( a - b di mana a> b ) menggunakan langkah 0,1 (dari 100 menjadi 0,1) kita memiliki ~ 34% kemungkinan kesalahan presisi . Berikut ini beberapa contohnya:

0.6 - 0.2 = 0.39999999999999997 (SMALLER)
0.5 - 0.4 = 0.09999999999999998 (SMALLER)
...
2.1 - 0.2 = 1.9000000000000001 (BIGGER)
2.0 - 1.9 = 0.10000000000000009 (BIGGER)
...
100 - 99.9 = 0.09999999999999432 (SMALLER)
100 - 99.8 = 0.20000000000000284 (BIGGER)

* 15% dan 34% memang besar, jadi selalu gunakan BigDecimal ketika presisi sangat penting. Dengan 2 digit desimal (langkah 0,01) situasinya sedikit lebih buruk (18% dan 36%).

Kostas Chalkias
sumber
28

Tidak, tidak rusak, tetapi sebagian besar pecahan desimal harus didekati

Ringkasan

Aritmatika floating point adalah tepat, sayangnya, itu tidak cocok dengan baik dengan biasa basis-10 nomor representasi kami, jadi ternyata kita sering memberikan masukan yang sedikit off dari apa yang kita tulis.

Bahkan bilangan sederhana seperti 0,01, 0,02, 0,03, 0,04 ... 0,24 tidak dapat direpresentasikan dengan tepat seperti pecahan biner. Jika Anda menghitung 0,01, .02, .03 ..., tidak sampai Anda mencapai 0,25 Anda akan mendapatkan fraksi pertama yang diwakili dalam basis 2 . Jika Anda mencoba menggunakan FP, 0,01 Anda akan sedikit mati, jadi satu-satunya cara untuk menambahkan 25 dari mereka ke tepat 0,25 akan membutuhkan rantai kausalitas panjang yang melibatkan bit penjaga dan pembulatan. Sulit diprediksi sehingga kami angkat tangan dan berkata "FP tidak tepat", tapi itu tidak sepenuhnya benar.

Kami terus-menerus memberikan perangkat keras FP sesuatu yang tampaknya sederhana di basis 10 tetapi merupakan pecahan berulang di basis 2.

Bagaimana ini bisa terjadi?

Ketika kita menulis dalam desimal, setiap fraksi (khususnya, setiap desimal terminating) adalah bilangan rasional dari formulir

           a / (2 n x 5 m )

Dalam biner, kita hanya mendapatkan istilah 2 n , yaitu:

           a / 2 n

Jadi dalam desimal, kita tidak dapat mewakili 1 / 3 . Karena basis 10 menyertakan 2 sebagai faktor prima, setiap angka yang dapat kita tulis sebagai fraksi biner juga dapat ditulis sebagai fraksi basis 10. Namun, hampir tidak ada apa pun yang kita tulis sebagai basis 10 yang dapat diwakili dalam biner. Dalam kisaran mulai 0,01, 0,02, 0,03 ... 0,99, hanya tiga angka yang dapat direpresentasikan dalam format FP kami: 0,25, 0,50, dan 0,75, karena semuanya 1/4, 1/2, dan 3/4, semua angka dengan faktor prima hanya menggunakan istilah 2 n .

Dalam basis 10 kita tidak dapat mewakili 1 / 3 . Namun dalam biner, kita tidak bisa melakukan 1 / 10 atau 1 / 3 .

Jadi sementara setiap pecahan biner dapat ditulis dalam desimal, kebalikannya tidak benar. Dan pada kenyataannya sebagian besar pecahan desimal berulang dalam biner.

Berhadapan dengannya

Pengembang biasanya diinstruksikan untuk melakukan perbandingan <epsilon , saran yang lebih baik mungkin untuk membulatkan ke nilai-nilai integral (dalam perpustakaan C: round () dan roundf (), yaitu, tetap dalam format FP) dan kemudian membandingkan. Pembulatan ke panjang fraksi desimal spesifik memecahkan sebagian besar masalah dengan output.

Juga, pada masalah angka-angka nyata (masalah-masalah yang ditemukan oleh FP pada komputer-komputer awal, yang sangat mahal), konstanta fisik alam semesta dan semua pengukuran lainnya hanya diketahui oleh angka-angka signifikan yang relatif kecil, sehingga seluruh ruang masalah "tidak eksak". FP "akurasi" bukan masalah dalam aplikasi semacam ini.

Seluruh masalah benar-benar muncul ketika orang mencoba menggunakan FP untuk penghitungan kacang. Itu memang bekerja untuk itu, tetapi hanya jika Anda tetap pada nilai-nilai integral, jenis yang mengalahkan titik menggunakannya. Inilah sebabnya kami memiliki semua pustaka perangkat lunak pecahan desimal itu.

Saya suka jawaban Pizza oleh Chris , karena itu menggambarkan masalah yang sebenarnya, bukan hanya handwaving biasa tentang "ketidaktepatan". Jika FP hanya "tidak akurat", kita bisa memperbaikinya dan akan melakukannya puluhan tahun yang lalu. Alasan kami belum melakukannya adalah karena format FP kompak dan cepat dan ini adalah cara terbaik untuk mengolah banyak angka. Juga, ini adalah warisan dari era ruang dan perlombaan senjata dan upaya awal untuk memecahkan masalah besar dengan komputer yang sangat lambat menggunakan sistem memori kecil. (Kadang-kadang, masing-masing inti magnetik untuk penyimpanan 1-bit, tapi itu cerita lain. )

Kesimpulan

Jika Anda hanya menghitung kacang di bank, solusi perangkat lunak yang menggunakan representasi string desimal di tempat pertama bekerja dengan sangat baik. Tetapi Anda tidak bisa melakukan kuantum chromodinamika atau aerodinamika dengan cara itu.

DigitalRoss
sumber
Membulatkan ke bilangan bulat terdekat bukanlah cara yang aman untuk menyelesaikan masalah perbandingan dalam semua kasus. Putaran 0.4999998 dan 0.500001 untuk bilangan bulat yang berbeda, jadi ada "zona bahaya" di sekitar setiap titik potong pembulatan. (Saya tahu string desimal itu mungkin tidak dapat direpresentasikan sebagai pelampung biner IEEE.)
Peter Cordes
1
Juga, meskipun floating point adalah format "legacy", itu dirancang dengan sangat baik. Saya tidak tahu apa pun yang akan diubah siapa pun jika merancang ulang sekarang. Semakin saya mempelajarinya, semakin saya pikir itu dirancang dengan sangat baik . misalnya eksponen yang bias berarti pelampung biner berurutan memiliki representasi integer berurutan, sehingga Anda dapat menerapkan nextafter()dengan kenaikan atau penurunan integer pada representasi biner dari pelampung IEEE. Selain itu, Anda dapat membandingkan float sebagai bilangan bulat dan mendapatkan jawaban yang benar kecuali bila keduanya negatif (karena tanda-magnitude vs komplemen 2's).
Peter Cordes
Saya tidak setuju, mengapung harus disimpan sebagai desimal dan bukan biner dan semua masalah diselesaikan.
Ronen Festinger
Tidakkah seharusnya " x / (2 ^ n + 5 ^ n) " menjadi " x / (2 ^ n * 5 ^ n) "?
Wai Ha Lee
@RonenFestinger - bagaimana dengan 1/3?
Stephen C
19

Apakah Anda mencoba solusi lakban?

Cobalah untuk menentukan kapan kesalahan terjadi dan memperbaikinya dengan pernyataan pendek jika, itu tidak cantik tetapi untuk beberapa masalah itu adalah satu-satunya solusi dan ini adalah salah satunya.

 if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
                    else { return n * 0.1 + 0.000000000000001 ;}    

Saya memiliki masalah yang sama dalam proyek simulasi ilmiah di c #, dan saya dapat memberitahu Anda bahwa jika Anda mengabaikan efek kupu-kupu itu akan berubah menjadi naga besar dan menggigit Anda di a **

alur kerja
sumber
19

Untuk menawarkan solusi terbaik saya dapat mengatakan saya menemukan metode berikut:

parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3

Izinkan saya menjelaskan mengapa itu solusi terbaik. Seperti yang disebutkan di atas, ada baiknya menggunakan fungsi Javascript toFixed () yang siap pakai untuk menyelesaikan masalah. Tetapi kemungkinan besar Anda akan menghadapi beberapa masalah.

Bayangkan Anda akan menambahkan dua angka float seperti 0.2dan 0.7di sini adalah: 0.2 + 0.7 = 0.8999999999999999.

Hasil yang Anda harapkan adalah 0.9itu berarti Anda membutuhkan hasil dengan ketelitian 1 digit dalam hal ini. Jadi Anda seharusnya menggunakan (0.2 + 0.7).tofixed(1) tetapi Anda tidak bisa hanya memberikan parameter tertentu untuk toFixed () karena itu tergantung pada angka yang diberikan, misalnya

`0.22 + 0.7 = 0.9199999999999999`

Dalam contoh ini Anda membutuhkan 2 digit presisi sehingga harus demikian toFixed(2), jadi apa yang harus paramter agar sesuai dengan setiap angka float yang diberikan?

Anda bisa mengatakan biarlah 10 dalam setiap situasi saat itu:

(0.2 + 0.7).toFixed(10) => Result will be "0.9000000000"

Sial! Apa yang akan Anda lakukan dengan angka nol yang tidak diinginkan setelah jam 9? Saatnya mengubahnya menjadi mengambang agar sesuai keinginan:

parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9

Sekarang setelah Anda menemukan solusinya, lebih baik menawarkannya sebagai fungsi seperti ini:

function floatify(number){
           return parseFloat((number).toFixed(10));
        }

Mari kita coba sendiri:

function floatify(number){
       return parseFloat((number).toFixed(10));
    }
 
function addUp(){
  var number1 = +$("#number1").val();
  var number2 = +$("#number2").val();
  var unexpectedResult = number1 + number2;
  var expectedResult = floatify(number1 + number2);
  $("#unexpectedResult").text(unexpectedResult);
  $("#expectedResult").text(expectedResult);
}
addUp();
input{
  width: 50px;
}
#expectedResult{
color: green;
}
#unexpectedResult{
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="number1" value="0.2" onclick="addUp()" onkeyup="addUp()"/> +
<input id="number2" value="0.7" onclick="addUp()" onkeyup="addUp()"/> =
<p>Expected Result: <span id="expectedResult"></span></p>
<p>Unexpected Result: <span id="unexpectedResult"></span></p>

Anda dapat menggunakannya dengan cara ini:

var x = 0.2 + 0.7;
floatify(x);  => Result: 0.9

Seperti yang disarankan W3SCHOOLS ada solusi lain juga, Anda dapat melipatgandakan dan membagi untuk memecahkan masalah di atas:

var x = (0.2 * 10 + 0.1 * 10) / 10;       // x will be 0.3

Ingatlah bahwa (0.2 + 0.1) * 10 / 10itu tidak akan bekerja sama sekali meskipun tampaknya sama! Saya lebih suka solusi pertama karena saya bisa menerapkannya sebagai fungsi yang mengubah float input ke float output akurat.

Mohammad Musavi
sumber
ini membuat saya benar-benar sakit kepala. Saya menjumlahkan 12 angka float, lalu menunjukkan jumlah dan rata-rata jika angka-angka itu. menggunakan toFixed () dapat memperbaiki penjumlahan dari 2 angka, tetapi ketika menjumlahkan beberapa angka, lompatannya signifikan.
Nuryagdy Mustapayev
@Nuryagdy Mustapayev Saya tidak mendapatkan niat Anda, karena saya menguji sebelum Anda dapat menjumlahkan 12 angka float, kemudian menggunakan fungsi floatify () pada hasilnya, lalu melakukan apa pun yang Anda inginkan, saya mengamati tidak ada masalah menggunakannya.
Mohammad Musavi
Saya hanya mengatakan dalam situasi saya di mana saya memiliki sekitar 20 parameter dan 20 rumus di mana hasil setiap rumus tergantung pada yang lain solusi ini tidak membantu.
Nuryagdy Mustapayev
16

Angka-angka aneh itu muncul karena komputer menggunakan sistem angka biner (basis 2) untuk keperluan perhitungan, sementara kami menggunakan desimal (basis 10).

Ada sebagian besar bilangan pecahan yang tidak dapat direpresentasikan secara tepat dalam biner atau dalam desimal atau keduanya. Hasil - Hasil angka yang dibulatkan (tetapi tepat).

Piyush S528
sumber
Saya tidak mengerti paragraf kedua Anda sama sekali.
Nae
1
@Nae Saya akan menerjemahkan paragraf kedua sebagai "Mayoritas fraksi tidak dapat diwakili secara tepat dalam desimal atau biner. Jadi sebagian besar hasil akan dibulatkan - meskipun mereka masih akan tepat dengan jumlah bit / digit yang melekat dalam representasi sedang digunakan. "
Steve Summit
15

Banyak dari banyak duplikat pertanyaan ini bertanya tentang efek pembulatan titik mengambang pada angka tertentu. Dalam praktiknya, lebih mudah untuk merasakan bagaimana cara kerjanya dengan melihat hasil perhitungan bunga yang tepat daripada hanya dengan membacanya. Beberapa bahasa menyediakan cara untuk melakukan itu - seperti mengubah a floatatau doubleke BigDecimaldalam Java.

Karena ini adalah pertanyaan bahasa-agnostik, maka perlu alat bahasa-agnostik, seperti Decimal to Floating-Point Converter .

Menerapkannya ke angka-angka dalam pertanyaan, diperlakukan sebagai ganda:

0,1 mengonversi menjadi 0,1000000000000000055511151231257827021181583404541015625,

0,2 mengonversi menjadi 0,200000000000000011102230246251565404236316680908203125,

0,3 dikonversi ke 0,299999999999999988897769753748434595763683319091796875, dan

0,3000000000000000000 dikonversi menjadi 0,3000000000000000444089209850062616169452667236328125.

Menambahkan dua angka pertama secara manual atau dalam kalkulator desimal seperti Full Precision Calculator , menunjukkan jumlah yang tepat dari input aktual adalah 0,3000000000000000166533453693773481063544750213623046875.

Jika dibulatkan menjadi setara dengan 0,3 kesalahan pembulatan akan menjadi 0,0000000000000000277555756156289135105907917022705078125. Pembulatan hingga setara dengan 0,30000000000000000004 juga memberikan kesalahan pembulatan 0,000000000000000000277555756156289135105907917022705078125. Pemutus dasi bulat-ke-rata berlaku.

Kembali ke konverter titik mengambang, heksadesimal mentah untuk 0,30000000000000004 adalah 3fd3333333333334, yang berakhir dengan angka genap dan karenanya merupakan hasil yang benar.

Patricia Shanahan
sumber
2
Untuk orang yang editnya baru saja saya putar kembali: Saya menganggap kutipan kode yang sesuai untuk mengutip kode. Jawaban ini, karena bahasa-netral, tidak mengandung kode yang dikutip sama sekali. Angka dapat digunakan dalam kalimat bahasa Inggris dan itu tidak mengubahnya menjadi kode.
Patricia Shanahan
Ini mungkin mengapa seseorang memformat angka Anda sebagai kode - bukan untuk memformat, tetapi untuk keterbacaan.
Wai Ha Lee
... juga, yang bulat untuk bahkan mengacu pada biner representasi, bukan yang desimal representasi. Lihat ini atau, misalnya, ini .
Wai Ha Lee
@ HaiHaLee Saya tidak menerapkan tes ganjil / genap untuk semua angka desimal, hanya heksadesimal. Digit heksadesimal adalah bahkan jika, dan hanya jika, bit terkecil dari ekspansi binernya adalah nol.
Patricia Shanahan
14

Mengingat bahwa tidak ada yang menyebutkan ini ...

Beberapa bahasa tingkat tinggi seperti Python dan Java datang dengan alat untuk mengatasi batasan titik mengambang biner. Sebagai contoh:

  • decimalModul Python dan BigDecimalkelas Java , yang mewakili angka secara internal dengan notasi desimal (sebagai lawan dari notasi biner). Keduanya memiliki presisi terbatas, sehingga mereka masih rentan kesalahan, namun mereka memecahkan masalah yang paling umum dengan aritmatika floating point biner.

    Desimal sangat baik ketika berhadapan dengan uang: sepuluh sen ditambah dua puluh sen selalu persis tiga puluh sen:

    >>> 0.1 + 0.2 == 0.3
    False
    >>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
    True
    

    decimalModul Python didasarkan pada standar IEEE 854-1987 .

  • fractionsModul Python dan BigFractionkelas Apache Common . Keduanya mewakili bilangan rasional sebagai (numerator, denominator)pasangan dan mereka dapat memberikan hasil yang lebih akurat daripada aritmatika floating point desimal.

Tidak satu pun dari solusi ini yang sempurna (terutama jika kita melihat kinerja, atau jika kita membutuhkan presisi yang sangat tinggi), tetapi tetap saja mereka memecahkan sejumlah besar masalah dengan aritmatika floating point biner.

Andrea Corbellini
sumber
14

Bisakah saya menambahkan; orang selalu menganggap ini sebagai masalah komputer, tetapi jika Anda menghitung dengan tangan Anda (basis 10), Anda tidak bisa mendapatkan (1/3+1/3=2/3)=truekecuali Anda memiliki infinity untuk menambahkan 0,333 ... menjadi 0,333 ... demikian pula dengan (1/10+2/10)!==3/10masalah pada basis 2, Anda memotongnya menjadi 0,333 + 0,333 = 0,666 dan mungkin membulatkannya menjadi 0,667 yang juga secara teknis tidak akurat.

Hitung di ternary, dan pertiga bukan masalah - mungkin beberapa ras dengan 15 jari di setiap tangan akan bertanya mengapa matematika desimal Anda rusak ...


sumber
Karena manusia menggunakan angka desimal, saya tidak melihat alasan mengapa mengapung tidak direpresentasikan sebagai desimal secara default sehingga kami memiliki hasil yang akurat.
Ronen Festinger
Manusia menggunakan banyak pangkalan selain basis 10 (desimal), biner adalah yang paling banyak kami gunakan untuk komputasi .. 'alasan bagus' adalah bahwa Anda tidak dapat mewakili setiap fraksi di setiap basis ..
@RonenFestinger biner aritmatika mudah diterapkan pada komputer karena hanya membutuhkan delapan operasi dasar dengan digit: katakanlah $ a $, $ b $ in $ 0,1 $ yang perlu Anda ketahui adalah $ \ operatorname {xor} (a, b) $ dan $ \ operatorname {cb} (a, b) $, di mana xor adalah eksklusif atau dan cb adalah "carry bit" yang adalah $ 0 $ dalam semua kasus kecuali ketika $ a = 1 = b $, dalam hal ini kami memiliki satu (sebenarnya komutatifitas semua operasi menghemat $ 2 $ case dan yang Anda butuhkan hanyalah $ 6 $ rules). Perluasan desimal membutuhkan $ 10 \ kali 11 $ (dalam notasi desimal) untuk disimpan dan $ 10 $ untuk kondisi yang berbeda untuk setiap bit dan membuang-buang penyimpanan saat dibawa.
Oskar Limka
@RonenFestinger - Desimal TIDAK lebih akurat. Itulah yang dikatakan jawaban ini. Untuk setiap basis yang Anda pilih, akan ada bilangan rasional (pecahan) yang memberikan urutan digit berulang berulang. Sebagai catatan, beberapa komputer pertama memang menggunakan representasi basis 10 untuk angka, tetapi perancang perangkat keras komputer perintis segera menyimpulkan bahwa basis 2 jauh lebih mudah dan lebih efisien untuk diimplementasikan.
Stephen C
9

Jenis matematika floating-point yang dapat diimplementasikan dalam komputer digital harus menggunakan perkiraan dari bilangan real dan operasi pada mereka. (Versi standar berjalan hingga lebih dari lima puluh halaman dokumentasi dan memiliki komite untuk menangani errata dan penyempurnaan lebih lanjut.)

Perkiraan ini adalah campuran perkiraan dari berbagai jenis, yang masing-masing dapat diabaikan atau diperhitungkan dengan cermat karena cara penyimpangan khusus dari ketelitian. Ini juga melibatkan sejumlah kasus luar biasa eksplisit pada tingkat perangkat keras dan perangkat lunak yang kebanyakan orang jalani saat berpura-pura tidak menyadarinya.

Jika Anda membutuhkan ketelitian tak terbatas (menggunakan angka π, misalnya, alih-alih salah satu dari stand-in yang lebih pendek), Anda harus menulis atau menggunakan program matematika simbolik sebagai gantinya.

Tetapi jika Anda baik-baik saja dengan gagasan bahwa kadang-kadang matematika floating-point tidak jelas dalam nilai dan logika dan kesalahan dapat terakumulasi dengan cepat, dan Anda dapat menulis persyaratan dan tes untuk memungkinkannya, maka kode Anda sering dapat bertahan dengan apa yang ada di dalam FPU Anda.

Blair Houghton
sumber
9

Hanya untuk bersenang-senang, saya bermain dengan representasi float, mengikuti definisi dari Standard C99 dan saya menulis kode di bawah ini.

Kode mencetak representasi biner dari float dalam 3 kelompok yang terpisah

SIGN EXPONENT FRACTION

dan setelah itu mencetak jumlah, bahwa, ketika dijumlahkan dengan cukup presisi, itu akan menunjukkan nilai yang benar-benar ada dalam perangkat keras.

Jadi ketika Anda menulis float x = 999..., kompiler akan mengubah angka itu dalam representasi bit yang dicetak oleh fungsi xxsedemikian sehingga jumlah yang dicetak oleh fungsi yysama dengan angka yang diberikan.

Pada kenyataannya, jumlah ini hanya perkiraan. Untuk angka 999.999.999 kompiler akan memasukkan bit representasi float angka 1.000.000.000

Setelah kode saya lampirkan sesi konsol, di mana saya menghitung jumlah istilah untuk kedua konstanta (minus PI dan 999999999) yang benar-benar ada di perangkat keras, dimasukkan di sana oleh kompiler.

#include <stdio.h>
#include <limits.h>

void
xx(float *x)
{
    unsigned char i = sizeof(*x)*CHAR_BIT-1;
    do {
        switch (i) {
        case 31:
             printf("sign:");
             break;
        case 30:
             printf("exponent:");
             break;
        case 23:
             printf("fraction:");
             break;

        }
        char b=(*(unsigned long long*)x&((unsigned long long)1<<i))!=0;
        printf("%d ", b);
    } while (i--);
    printf("\n");
}

void
yy(float a)
{
    int sign=!(*(unsigned long long*)&a&((unsigned long long)1<<31));
    int fraction = ((1<<23)-1)&(*(int*)&a);
    int exponent = (255&((*(int*)&a)>>23))-127;

    printf(sign?"positive" " ( 1+":"negative" " ( 1+");
    unsigned int i = 1<<22;
    unsigned int j = 1;
    do {
        char b=(fraction&i)!=0;
        b&&(printf("1/(%d) %c", 1<<j, (fraction&(i-1))?'+':')' ), 0);
    } while (j++, i>>=1);

    printf("*2^%d", exponent);
    printf("\n");
}

void
main()
{
    float x=-3.14;
    float y=999999999;
    printf("%lu\n", sizeof(x));
    xx(&x);
    xx(&y);
    yy(x);
    yy(y);
}

Berikut adalah sesi konsol di mana saya menghitung nilai nyata float yang ada di perangkat keras. Saya biasa bcmencetak jumlah persyaratan yang dihasilkan oleh program utama. Satu dapat memasukkan jumlah itu di python replatau yang serupa juga.

-- .../terra1/stub
@ qemacs f.c
-- .../terra1/stub
@ gcc f.c
-- .../terra1/stub
@ ./a.out
sign:1 exponent:1 0 0 0 0 0 0 fraction:0 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 1 0 0 0 0 1 1
sign:0 exponent:1 0 0 1 1 1 0 fraction:0 1 1 0 1 1 1 0 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 0
negative ( 1+1/(2) +1/(16) +1/(256) +1/(512) +1/(1024) +1/(2048) +1/(8192) +1/(32768) +1/(65536) +1/(131072) +1/(4194304) +1/(8388608) )*2^1
positive ( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
-- .../terra1/stub
@ bc
scale=15
( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
999999999.999999446351872

Itu dia. Nilai 999999999 sebenarnya

999999999.999999446351872

Anda juga dapat memeriksa bcbahwa -3.14 juga terganggu. Jangan lupa untuk mengatur scalefaktor bc.

Jumlah yang ditampilkan adalah apa yang ada di dalam perangkat keras. Nilai yang Anda peroleh dengan menghitungnya tergantung pada skala yang Anda tetapkan. Saya memang mengatur scalefaktor ke 15. Secara matematis, dengan ketepatan tak terbatas, tampaknya 1.000.000.000.

alinsoar
sumber
5

Cara lain untuk melihatnya: Digunakan adalah 64 bit untuk mewakili angka. Akibatnya tidak ada cara lebih dari 2 ** 64 = 18.446.744.073.709.551.616 angka yang berbeda dapat diwakili secara tepat.

Namun, Math mengatakan sudah ada banyak desimal tak terhingga antara 0 dan 1. IEE 754 mendefinisikan pengkodean untuk menggunakan 64 bit ini secara efisien untuk ruang angka yang jauh lebih besar plus NaN dan +/- Infinity, sehingga ada kesenjangan antara angka-angka yang diwakili secara akurat yang diisi dengan angka hanya didekati.

Sayangnya 0,3 duduk di celah.

Torsten Becker
sumber
4

Bayangkan bekerja di basis sepuluh dengan, katakanlah, 8 digit akurasi. Anda memeriksa apakah

1/3 + 2 / 3 == 1

dan belajar bahwa ini kembali false. Mengapa? Yah, seperti bilangan real yang kita miliki

1/3 = 0,333 .... dan 2/3 = 0,666 ....

Memotong di delapan tempat desimal, kita dapatkan

0.33333333 + 0.66666666 = 0.99999999

yang tentu saja berbeda dari yang 1.00000000persis 0.00000001.


Situasi untuk nomor biner dengan jumlah bit tetap persis analog. Sebagai bilangan real, kami punya

1/10 = 0,0001100110011001100 ... (basis 2)

dan

1/5 = 0,0011001100110011001 ... (basis 2)

Jika kita memotong ini menjadi, katakanlah, tujuh bit, maka kita akan mendapatkannya

0.0001100 + 0.0011001 = 0.0100101

sementara di sisi lain,

3/10 = 0,01001100110011 ... (basis 2)

yang, terpotong menjadi tujuh bit, adalah 0.0100110, dan ini berbeda persis 0.0000001.


Situasi tepatnya sedikit lebih halus karena angka-angka ini biasanya disimpan dalam notasi ilmiah. Jadi, misalnya, alih-alih menyimpan 1/10 karena 0.0001100kami dapat menyimpannya sebagai sesuatu 1.10011 * 2^-4, tergantung pada berapa banyak bit yang telah kami alokasikan untuk eksponen dan mantissa. Ini memengaruhi berapa banyak digit presisi yang Anda dapatkan untuk perhitungan Anda.

Hasilnya adalah bahwa karena kesalahan pembulatan ini Anda pada dasarnya tidak pernah ingin menggunakan == pada angka floating-point. Sebagai gantinya, Anda dapat memeriksa apakah nilai absolut dari selisihnya lebih kecil daripada beberapa angka kecil tetap.

Daniel McLaury
sumber
4

Karena Python 3.5 Anda dapat menggunakan math.isclose()fungsi untuk menguji perkiraan persamaan:

>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True
>>> 0.1 + 0.2 == 0.3
False
nauer
sumber
3

Karena utas ini bercabang sedikit menjadi diskusi umum tentang implementasi floating point saat ini, saya akan menambahkan bahwa ada proyek untuk memperbaiki masalah mereka.

Lihatlah https://posithub.org/ misalnya, yang menampilkan tipe nomor yang disebut posit (dan pendahulunya unum) yang berjanji untuk menawarkan akurasi yang lebih baik dengan bit yang lebih sedikit. Jika pemahaman saya benar, itu juga memperbaiki jenis masalah dalam pertanyaan. Proyek yang cukup menarik, orang di belakangnya adalah ahli matematika itu Dr. John Gustafson . Semuanya adalah open source, dengan banyak implementasi aktual di C / C ++, Python, Julia dan C # ( https://hastlayer.com/arithmetics ).

Piedone
sumber
3

Ini sebenarnya cukup sederhana. Ketika Anda memiliki sistem basis 10 (seperti sistem kami), itu hanya dapat mengekspresikan pecahan yang menggunakan faktor utama basis. Faktor prima dari 10 adalah 2 dan 5. Jadi 1/2, 1/4, 1/5, 1/8, dan 1/10 semuanya dapat dinyatakan dengan bersih karena penyebut semuanya menggunakan faktor prima 10. Sebaliknya, 1 / 3, 1/6, dan 1/7 semuanya desimal berulang karena penyebutnya menggunakan faktor prima 3 atau 7. Dalam biner (atau basis 2), satu-satunya faktor prima adalah 2. Jadi Anda hanya dapat mengekspresikan pecahan dengan rapi yang hanya mengandung 2 sebagai faktor utama. Dalam biner, 1/2, 1/4, 1/8 semuanya akan dinyatakan dengan jelas sebagai desimal. Sementara, 1/5 atau 1/10 akan mengulangi desimal. Jadi 0,1 dan 0,2 (1/10 dan 1/5) saat membersihkan desimal dalam sistem basis 10, mengulangi desimal dalam sistem basis 2 yang dioperasikan komputer. Ketika Anda melakukan matematika pada desimal berulang ini,

Dari https://0.30000000000000004.com/

Vlad Agurets
sumber
3

Angka desimal seperti 0.1, 0.2, dan 0.3tidak diwakili tepat dalam biner dikodekan tipe floating point. Jumlah perkiraan untuk 0.1dan 0.2berbeda dari perkiraan yang digunakan untuk 0.3, maka kepalsuan 0.1 + 0.2 == 0.3seperti dapat dilihat lebih jelas di sini:

#include <stdio.h>

int main() {
    printf("0.1 + 0.2 == 0.3 is %s\n", 0.1 + 0.2 == 0.3 ? "true" : "false");
    printf("0.1 is %.23f\n", 0.1);
    printf("0.2 is %.23f\n", 0.2);
    printf("0.1 + 0.2 is %.23f\n", 0.1 + 0.2);
    printf("0.3 is %.23f\n", 0.3);
    printf("0.3 - (0.1 + 0.2) is %g\n", 0.3 - (0.1 + 0.2));
    return 0;
}

Keluaran:

0.1 + 0.2 == 0.3 is false
0.1 is 0.10000000000000000555112
0.2 is 0.20000000000000001110223
0.1 + 0.2 is 0.30000000000000004440892
0.3 is 0.29999999999999998889777
0.3 - (0.1 + 0.2) is -5.55112e-17

Agar perhitungan ini dapat dievaluasi lebih andal, Anda perlu menggunakan representasi berbasis desimal untuk nilai floating point. Standar C tidak menentukan tipe seperti itu secara default tetapi sebagai ekstensi yang dijelaskan dalam Laporan teknis .

Tipe _Decimal32, _Decimal64dan _Decimal128mungkin tersedia di sistem Anda (misalnya, GCC mendukungnya pada target yang dipilih , tetapi Dentang tidak mendukungnya pada OS X ).

chqrlie
sumber
1

Math.sum (javascript) .... jenis penggantian operator

.1 + .0001 + -.1 --> 0.00010000000000000286
Math.sum(.1 , .0001, -.1) --> 0.0001

Object.defineProperties(Math, {
    sign: {
        value: function (x) {
            return x ? x < 0 ? -1 : 1 : 0;
            }
        },
    precision: {
        value: function (value, precision, type) {
            var v = parseFloat(value), 
                p = Math.max(precision, 0) || 0, 
                t = type || 'round';
            return (Math[t](v * Math.pow(10, p)) / Math.pow(10, p)).toFixed(p);
        }
    },
    scientific_to_num: {  // this is from https://gist.github.com/jiggzson
        value: function (num) {
            //if the number is in scientific notation remove it
            if (/e/i.test(num)) {
                var zero = '0',
                        parts = String(num).toLowerCase().split('e'), //split into coeff and exponent
                        e = parts.pop(), //store the exponential part
                        l = Math.abs(e), //get the number of zeros
                        sign = e / l,
                        coeff_array = parts[0].split('.');
                if (sign === -1) {
                    num = zero + '.' + new Array(l).join(zero) + coeff_array.join('');
                } else {
                    var dec = coeff_array[1];
                    if (dec)
                        l = l - dec.length;
                    num = coeff_array.join('') + new Array(l + 1).join(zero);
                }
            }
            return num;
         }
     }
    get_precision: {
        value: function (number) {
            var arr = Math.scientific_to_num((number + "")).split(".");
            return arr[1] ? arr[1].length : 0;
        }
    },
    sum: {
        value: function () {
            var prec = 0, sum = 0;
            for (var i = 0; i < arguments.length; i++) {
                prec = this.max(prec, this.get_precision(arguments[i]));
                sum += +arguments[i]; // force float to convert strings to number
            }
            return Math.precision(sum, prec);
        }
    }
});

idenya adalah menggunakan Matematika sebagai gantinya operator untuk menghindari kesalahan float

Math.sum secara otomatis mendeteksi ketepatan untuk digunakan

Math.sum menerima sejumlah argumen

bortunac
sumber
1
Saya tidak yakin Anda telah menjawab pertanyaan, " Mengapa ketidakakuratan ini terjadi? "
Wai Ha Lee
dengan cara Anda benar tapi saya datang ke sini dari perilaku aneh javascript tentang masalah ini ... saya hanya ingin berbagi jenis solusi
bortunac
Anda masih belum menjawab pertanyaan itu.
Wai Ha Lee
k Anda punya masalah dengan ini ... beri tahu saya di mana harus memindahkannya atau jika Anda bersikeras saya bisa menghapusnya
bortunac
0

Saya baru saja melihat masalah menarik ini di sekitar floating point:

Pertimbangkan hasil berikut:

error = (2**53+1) - int(float(2**53+1))
>>> (2**53+1) - int(float(2**53+1))
1

Kita dapat dengan jelas melihat breakpoint ketika 2**53+1- semua berfungsi dengan baik sampai 2**53.

>>> (2**53) - int(float(2**53))
0

Masukkan deskripsi gambar di sini

Ini terjadi karena biner presisi ganda: IEEE 754 format biner presisi ganda biner: binary64

Dari halaman Wikipedia untuk format floating-point presisi ganda :

Floating-point biner presisi ganda adalah format yang umum digunakan pada PC, karena jangkauannya yang lebih luas daripada floating point presisi tunggal, terlepas dari kinerja dan biaya bandwidth. Seperti dengan format floating-point presisi tunggal, ia tidak memiliki presisi pada bilangan bulat jika dibandingkan dengan format bilangan bulat dengan ukuran yang sama. Umumnya dikenal sebagai double. Standar IEEE 754 menentukan binary64 memiliki:

  • Tanda bit: 1 bit
  • Eksponen: 11 bit
  • Presisi yang signifikan: 53 bit (52 disimpan secara eksplisit)

Masukkan deskripsi gambar di sini

Nilai riil yang diasumsikan oleh datum presisi ganda 64-bit yang diberikan dengan eksponen yang bias dan fraksi 52-bit adalah

Masukkan deskripsi gambar di sini

atau

Masukkan deskripsi gambar di sini

Terima kasih kepada @a_guest karena menunjukkannya kepada saya.

costargc
sumber
-1

Pertanyaan berbeda telah dinamai duplikat untuk pertanyaan ini:

Di C ++, mengapa hasil cout << xberbeda dari nilai yang ditunjukkan oleh debugger x?

The xdalam pertanyaan adalah floatvariabel.

Salah satu contohnya

float x = 9.9F;

Debugger menunjukkan 9.89999962, output dari coutoperasi 9.9.

Jawabannya ternyata adalah coutpresisi default untuk floatadalah 6, sehingga membulatkan ke 6 angka desimal.

Lihat di sini untuk referensi


sumber
1
IMO - memposting ini di sini adalah pendekatan yang salah. Saya tahu ini membuat frustrasi, tetapi orang-orang yang membutuhkan jawaban untuk pertanyaan asli (tampaknya sekarang dihapus!) Tidak akan menemukannya di sini. Jika Anda benar-benar merasa bahwa pekerjaan Anda layak untuk diselamatkan, saya akan menyarankan: 1) mencari Q lain yang benar-benar menjawab, 2) membuat pertanyaan yang dijawab sendiri.
Stephen C