Saya tahu bahwa sebagian besar desimal tidak memiliki representasi floating point yang tepat ( Apakah matematika floating point rusak? ).
Tapi saya tidak melihat mengapa 4*0.1
dicetak dengan baik 0.4
, tetapi 3*0.1
tidak, ketika kedua nilai sebenarnya memiliki representasi desimal jelek:
>>> 3*0.1
0.30000000000000004
>>> 4*0.1
0.4
>>> from decimal import Decimal
>>> Decimal(3*0.1)
Decimal('0.3000000000000000444089209850062616169452667236328125')
>>> Decimal(4*0.1)
Decimal('0.40000000000000002220446049250313080847263336181640625')
0.3000000000000000444089209850062616169452667236328125
sebagai0.30000000000000004
dan0.40000000000000002220446049250313080847263336181640625
sebagai.4
meskipun mereka tampaknya memiliki akurasi yang sama, dan dengan demikian tidak menjawab pertanyaan itu.Jawaban:
Jawaban sederhananya adalah karena
3*0.1 != 0.3
kesalahan kuantisasi (pembulatan) (sedangkan4*0.1 == 0.4
karena mengalikan dengan kekuatan dua biasanya merupakan operasi "tepat").Anda dapat menggunakan
.hex
metode dalam Python untuk melihat representasi internal dari suatu bilangan (pada dasarnya, nilai titik mengambang biner yang tepat , daripada pendekatan basis-10). Ini dapat membantu menjelaskan apa yang terjadi di bawah tenda.0,1 adalah 0x1.999999999999a kali 2 ^ -4. Huruf "a" pada akhirnya berarti angka 10 - dengan kata lain, 0,1 dalam titik apung biner sangat sedikit lebih besar dari nilai "tepat" 0,1 (karena 0x0,99 akhir dibulatkan menjadi 0x0,a). Ketika Anda mengalikan ini dengan 4, kekuatan dua, eksponen bergeser ke atas (dari 2 ^ -4 ke 2 ^ -2) tetapi jumlahnya dinyatakan tidak berubah, jadi
4*0.1 == 0.4
.Namun, ketika Anda mengalikan 3, perbedaan kecil antara 0x0.99 dan 0x0.a0 (0x0.07) memperbesar menjadi kesalahan 0x0.15, yang muncul sebagai kesalahan satu digit di posisi terakhir. Ini menyebabkan 0,1 * 3 menjadi sedikit lebih besar dari nilai bulat 0,3.
Float Python 3
repr
dirancang untuk dapat trip-trippable , yaitu nilai yang ditampilkan harus benar-benar dapat dikonversi menjadi nilai aslinya. Oleh karena itu, ia tidak dapat menampilkan0.3
dan0.1*3
dengan cara yang persis sama, atau dua angka yang berbeda akan berakhir sama setelah tersandung. Akibatnya,repr
mesin Python 3 memilih untuk menampilkan satu dengan kesalahan yang agak jelas.sumber
.hex()
; Saya tidak tahu itu ada.)e
karena itu sudah hex digit. Mungkinp
untuk kekuatan, bukan eksponen .p
dalam konteks ini kembali (setidaknya) ke C99, dan juga muncul di IEEE 754 dan dalam berbagai bahasa lainnya (termasuk Jawa). Ketikafloat.hex
danfloat.fromhex
diimplementasikan (oleh saya :-), Python hanya menyalin apa yang pada saat itu ditetapkan praktiknya. Saya tidak tahu apakah niatnya adalah 'p' untuk "Kekuatan", tetapi sepertinya cara yang bagus untuk memikirkannya.repr
(danstr
dalam Python 3) akan mengeluarkan digit sebanyak yang diperlukan untuk membuat nilai tidak ambigu. Dalam hal ini hasil dari perkalian3*0.1
bukan nilai terdekat dengan 0,3 (0x1.3333333333333p-2 dalam hex), itu sebenarnya satu LSB lebih tinggi (0x1.333333333333334p-2) sehingga perlu lebih banyak digit untuk membedakannya dari 0,3.Di sisi lain, perkalian
4*0.1
tidak mendapatkan nilai terdekat ke 0,4 (0x1.999999999999ap-2 dalam hex), sehingga tidak memerlukan digit tambahan.Anda dapat memverifikasi ini dengan mudah:
Saya menggunakan notasi hex di atas karena bagus dan kompak dan menunjukkan perbedaan bit antara dua nilai. Anda dapat melakukannya sendiri menggunakan mis
(3*0.1).hex()
. Jika Anda lebih suka melihat mereka dalam semua kemuliaan desimal mereka, ini dia:sumber
3*0.1 == 0.3
dan4*0.1 == 0.4
?Inilah kesimpulan yang disederhanakan dari jawaban lain.
sumber
str
danrepr
identik untuk pelampung. Untuk Python 2.7,repr
memiliki properti yang Anda identifikasi, tetapistr
jauh lebih sederhana - itu hanya menghitung 12 digit signifikan dan menghasilkan string output berdasarkan pada mereka. Untuk Python <= 2.6, keduanyarepr
danstr
didasarkan pada jumlah tetap dari digit signifikan (17 untukrepr
, 12 untukstr
). (Dan tidak ada yang peduli tentang Python 3.0 atau Python 3.1 :-)repr
sehingga perilaku Python 2.7 akan identik ...Tidak benar-benar spesifik untuk implementasi Python tetapi harus diterapkan pada float untuk fungsi string desimal.
Angka floating point pada dasarnya adalah angka biner, tetapi dalam notasi ilmiah dengan batas tetap dari angka-angka penting.
Kebalikan dari angka apa pun yang memiliki faktor bilangan prima yang tidak dibagi dengan basis akan selalu menghasilkan representasi titik titik berulang. Misalnya 1/7 memiliki faktor prima, 7, yang tidak dibagi dengan 10, dan karenanya memiliki representasi desimal berulang, dan hal yang sama berlaku untuk 1/10 dengan faktor prima 2 dan 5, yang terakhir tidak dibagikan dengan 2 ; ini berarti bahwa 0,1 tidak dapat secara tepat diwakili oleh jumlah bit yang terbatas setelah titik titik.
Karena 0,1 tidak memiliki representasi yang tepat, fungsi yang mengubah perkiraan menjadi string titik desimal biasanya akan mencoba memperkirakan nilai tertentu sehingga mereka tidak mendapatkan hasil yang tidak intuitif seperti 0,1000000000004121.
Karena floating point dalam notasi ilmiah, setiap perkalian dengan kekuatan basis hanya mempengaruhi bagian eksponen dari angka tersebut. Misalnya 1.231e + 2 * 100 = 1.231e + 4 untuk notasi desimal, dan juga, 1.00101010e11 * 100 = 1.00101010e101 dalam notasi biner. Jika saya kalikan dengan non-power dari pangkalan, angka yang signifikan juga akan terpengaruh. Misalnya 1.2e1 * 3 = 3.6e1
Bergantung pada algoritma yang digunakan, mungkin mencoba menebak desimal umum berdasarkan angka signifikan saja. Baik 0,1 dan 0,4 memiliki angka signifikan yang sama dalam biner, karena mengapung mereka pada dasarnya pemotongan (8/5) (2 ^ -4) dan (8/5) (2 ^ -6). Jika algoritme mengidentifikasi pola sigfig 8/5 sebagai desimal 1.6, maka ia akan bekerja pada 0,1, 0,2, 0,4, 0,8, dll. Mungkin juga memiliki pola sigfig ajaib untuk kombinasi lain, seperti float 3 dibagi dengan float 10 dan pola ajaib lainnya secara statistik kemungkinan akan dibentuk oleh pembagian oleh 10.
Dalam kasus 3 * 0,1, beberapa angka penting terakhir kemungkinan akan berbeda dari membagi float 3 dengan float 10, menyebabkan algoritma gagal mengenali angka ajaib untuk konstanta 0.3 tergantung pada toleransinya terhadap kehilangan presisi.
Edit: https://docs.python.org/3.1/tutorial/floatingpoint.html
Tidak ada toleransi untuk kehilangan presisi, jika float x (0,3) tidak persis sama dengan float y (0,1 * 3), maka repr (x) tidak persis sama dengan repr (y).
sumber