Mengapa 1 // 0,01 == 99 dalam Python?

31

Saya membayangkan ini adalah pertanyaan presisi floating point klasik, tapi saya mencoba untuk membungkus kepala saya di sekitar hasil ini, berjalan 1//0.01dengan hasil Python 3.7.5 99.

Saya membayangkan ini adalah hasil yang diharapkan, tetapi apakah ada cara untuk memutuskan kapan lebih aman untuk digunakan int(1/f)daripada 1//f?

Albert James Teddy
sumber
4
Ya, selalu lebih aman int (1 / f). Hanya karena // adalah divisi FLOOR, dan Anda salah menganggapnya ROUND.
Perdi Estaquel
4
Kemungkinan rangkap dari apakah matematika floating point rusak?
pppery
2
Bukan duplikat. Ini dapat bekerja seperti yang diharapkan 99,99% dengan selalu menggunakan round()dan tidak pernah //atau int(). Pertanyaan terkait adalah tentang perbandingan float tidak ada hubungannya dengan pemotongan dan tidak ada perbaikan yang mudah.
maksimal

Jawaban:

23

Jika ini adalah pembagian dengan bilangan real, 1//0.01akan menjadi tepat 100. Karena mereka adalah perkiraan floating-point, 0.01sedikit lebih besar dari 1/100, berarti hasil bagi itu sedikit lebih kecil dari 100. Nilai 99 ini. Nilai tertentu yang kemudian dipasangkan ke 99.

chepner
sumber
3
Ini tidak membahas bagian "apakah ada cara untuk memutuskan kapan bagian itu lebih aman".
Scott Hunter
10
"Lebih aman" tidak didefinisikan dengan baik.
chepner
1
Cukup untuk sepenuhnya mengabaikannya, esp. ketika OP mengetahui masalah floating point?
Scott Hunter
3
@ chepner Jika "lebih aman" tidak didefinisikan dengan baik, maka mungkin lebih baik untuk meminta klarifikasi: /
2
cukup jelas bagi saya bahwa "lebih aman" berarti "kesalahan tidak lebih buruk daripada kalkulator saku murah"
maxy
9

Alasan untuk hasil ini seperti yang Anda sebutkan, dan dijelaskan dalam Apakah matematika floating point rusak? dan banyak tanya jawab serupa lainnya.

Saat Anda mengetahui jumlah desimal pembilang dan penyebut, cara yang lebih andal adalah mengalikan angka-angka itu terlebih dahulu sehingga mereka dapat diperlakukan sebagai bilangan bulat, dan kemudian melakukan pembagian bilangan bulat di atasnya:

Jadi dalam kasus Anda 1//0.01harus dikonversi dulu 1*100//(0.01*100)yang 100.

Dalam kasus yang lebih ekstrim, Anda masih bisa mendapatkan hasil yang "tidak terduga". Mungkin perlu menambahkan roundpanggilan ke pembilang dan penyebut sebelum melakukan pembagian integer:

1 * 100000000000 // round(0.00000000001 * 100000000000)

Tetapi, jika ini adalah tentang bekerja dengan desimal tetap (uang, sen), maka pertimbangkan bekerja dengan sen sebagai satuan , sehingga semua aritmatika dapat dilakukan sebagai bilangan bulat aritmatika, dan hanya mengkonversi ke / dari satuan moneter utama (dolar) saat melakukan I / O.

Atau sebagai alternatif, gunakan perpustakaan untuk desimal, seperti desimal , yang:

... memberikan dukungan untuk aritmatika floating point desimal yang dibulatkan dengan benar.

from decimal import Decimal
cent = Decimal(1) / Decimal(100) # Contrary to floating point, this is exactly 0.01
print (Decimal(1) // cent) # 100
trincot
sumber
3
"Yang jelas adalah 100." Belum tentu: jika .01 tidak tepat, maka .01 * 100 juga tidak. Itu harus "disetel" secara manual.
glglgl
8

Apa yang harus Anda memperhitungkan adalah bahwa //adalah flooroperator dan dengan demikian Anda pertama harus berpikir seperti jika Anda memiliki probabilitas yang sama untuk jatuh 100 seperti dalam 99 (*) (karena operasi akan 100 ± epsilondengan epsilon>0ketentuan bahwa kemungkinan mendapatkan persis 100.00 ..0 sangat rendah.)

Anda benar-benar dapat melihat hal yang sama dengan tanda minus,

>>> 1//.01
99.0
>>> -1//.01
-100.0

dan Anda harus kaget.

Di sisi lain, int(-1/.01)lakukan pertama divisi dan kemudian menerapkan int()dalam angka, yang bukan lantai tetapi pemotongan menuju 0 ! artinya dalam hal itu,

>>> 1/.01
100.0
>>> -1/.01
-100.0

karenanya,

>>> int(1/.01)
100
>>> int(-1/.01)
-100

Namun pembulatan, akan memberi Anda hasil yang ANDA harapkan untuk operator ini karena sekali lagi, kesalahannya kecil untuk angka-angka itu.

(*) Saya tidak mengatakan bahwa probabilitasnya sama, saya hanya mengatakan bahwa apriori ketika Anda melakukan perhitungan dengan aritmatika apung yang merupakan perkiraan dari apa yang Anda dapatkan.

myradio
sumber
7

Angka floating point tidak dapat mewakili angka desimal paling tepat, jadi ketika Anda mengetik floating point literal Anda benar-benar mendapatkan perkiraan dari literal itu. Perkiraan mungkin lebih besar atau lebih kecil dari angka yang Anda ketikkan.

Anda dapat melihat nilai tepat angka floating point dengan memberikannya ke Desimal atau Fraksi.

>>> from decimal import Decimal
>>> Decimal(0.01)
Decimal('0.01000000000000000020816681711721685132943093776702880859375')
>>> from fractions import Fractio
>>> Fraction(0.01)
Fraction(5764607523034235, 576460752303423488) 

Kita dapat menggunakan tipe Fraksi untuk menemukan kesalahan yang disebabkan oleh literal tidak tepat kita.

>>> float((Fraction(1)/Fraction(0.01)) - 100)
-2.0816681711721685e-15

Kita juga bisa mengetahui bagaimana angka floating point presisi granular ganda sekitar 100 adalah dengan menggunakan selanjutnya dari numpy.

>>> from numpy import nextafter
>>> nextafter(100,0)-100
-1.4210854715202004e-14

Dari sini kita dapat menduga bahwa angka floating point terdekat 1/0.01000000000000000020816681711721685132943093776702880859375sebenarnya adalah 100.

Perbedaan antara 1//0.01dan int(1/0.01)merupakan pembulatan. 1 // 0,01 membulatkan hasil yang pasti ke seluruh angka berikutnya dalam satu langkah. Jadi kami mendapatkan hasil 99.

int (1 / 0,01) di sisi lain putaran dalam dua tahap, pertama putaran hasil ke nomor floating point presisi ganda terdekat (yang persis 100), kemudian bulat bahwa angka floating point turun ke bilangan bulat berikutnya (yaitu lagi tepat 100).

plugwash
sumber
Menyebut pembulatan yang adil ini menyesatkan. Ini harus disebut pemotongan atau pembulatan ke nol : int(0.9) == 0danint(-0.9) == 0
maksimal
Ini adalah tipe floating point biner yang Anda bicarakan di sini. (Ada juga tipe floating point desimal juga.)
Stephen C
3

Jika Anda menjalankan yang berikut ini

from decimal import *

num = Decimal(1) / Decimal(0.01)
print(num)

Outputnya adalah:

99.99999999999999791833182883

Ini adalah bagaimana itu direpresentasikan secara internal, jadi membulatkannya //akan memberi99

Hujan
sumber
2
Ini cukup akurat untuk menunjukkan kesalahan dalam kasus ini, tetapi perlu diketahui bahwa aritmatika "Desimal" juga tidak tepat.
plugwash
Dengan Decimal(0.01)Anda terlambat, kesalahan sudah merayap masuk sebelum Anda menelepon Decimal. Saya tidak yakin bagaimana ini merupakan jawaban untuk pertanyaan ... Anda harus terlebih dahulu menghitung 0,01 dengan tepat Decimal(1) / Decimal(100), seperti yang saya tunjukkan dalam jawaban saya.
trincot
@trincot Jawaban saya adalah pertanyaan dalam judul "Mengapa 1 // 0,01 == 99" Saya mencoba menunjukkan kepada OP bagaimana bilangan mengambang diperlakukan secara internal.
Hujan