Python'da neden 1 // 0.01 == 99?


31

Bunun klasik bir kayan nokta hassasiyeti sorusu olduğunu düşünüyorum, ancak başımı bu sonucun etrafına sarmaya çalışıyorum 1//0.01, Python 3.7.5 verimiyle çalışıyor 99.

Beklenen bir sonuç olduğunu düşünüyorum, ancak ne zaman kullanmanın int(1/f)daha güvenli olduğuna karar vermenin bir yolu var 1//fmı?


4
Evet, her zaman daha güvenli int (1 / f). Çünkü // ZEMİN bölümüdür ve yanlış bir şekilde YUVARLA olduğunu düşünürsünüz.
Perdi Estaquel


2
Kopya değil. Bu her zaman round()ve asla //veya kullanarak% 99,99 beklendiği gibi çalışabilir int(). Bağlantılı soru şamandıra karşılaştırmasının kısaltma ve böyle kolay bir düzeltme ile ilgisi yoktur.
maxy

Yanıtlar:


23

Bu gerçek sayılarla bölünmüş 1//0.01olsaydı, tam olarak 100 olurdu. Kayan nokta yaklaşımları oldukları için, 0.011/100'den biraz daha büyüktür, yani bölüm 100'den biraz daha küçüktür. 99'a.


3
Bu, "ne zaman daha güvenli olduğuna karar vermenin bir yolu var mı" bölümüne değinmiyor.
Scott Hunter

10
"Daha güvenli" iyi tanımlanmamıştır.
chepner

1
Tamamen görmezden gelmeye yeter, esp. OP kayan nokta sorunlarının ne zaman farkında?
Scott Hunter

3
@ chepner "Daha güvenli" iyi tanımlanmamışsa, açıklama istemek daha iyi olabilir: /

2
"güvenli" ucuz cep hesap daha kötü değil "hata anlamına gelir" bana oldukça açık
maxy

9

Bu sonucun nedenleri sizin belirttiğiniz gibidir ve Kayan nokta matematiği kırık mı? ve daha birçok benzer Soru-Cevap.

Pay ve payda ondalıklarının sayısını bildiğinizde, daha güvenilir bir yol önce tamsayı olarak davranabilmeleri için bu sayıları çarpmak ve daha sonra tamsayı bölme yapmaktır:

Yani sizin durumunuzda 1//0.01önce 1*100//(0.01*100)100'e dönüştürülmelidir .

Daha uç durumlarda, yine de "beklenmedik" sonuçlar alabilirsiniz. roundTamsayı bölümünü gerçekleştirmeden önce pay ve paydaya bir çağrı eklemek gerekebilir :

1 * 100000000000 // round(0.00000000001 * 100000000000)

Ancak, bu sabit ondalıklarla (para, sent) çalışmakla ilgili ise, sentlerle birim olarak çalışmayı düşünün , böylece tüm aritmetik tamsayı aritmetiği olarak yapılabilir ve bunu yaparken yalnızca ana para birimine (dolar) dönüşebilir G / Ç.

Veya alternatif olarak, ondalık gibi ondalık sayılar için bir kütüphane kullanın :

... hızlı doğru yuvarlanan ondalık kayan nokta aritmetiği için destek sağlar.

from decimal import Decimal
cent = Decimal(1) / Decimal(100) # Contrary to floating point, this is exactly 0.01
print (Decimal(1) // cent) # 100

3
"100 olduğu açıktır." Mutlaka değil: .01 tam değilse, .01 * 100 de doğru değildir. Manuel olarak "ayarlanmalıdır".
glglgl

8

Ne dikkate almak zorunda olduğunu //olduğunu flooroperatör ve 99 deki gibi 100'de düşmeye eşit olasılığı sanki gibi ilk önce düşünmek gerekir (*) (operasyon olacak çünkü 100 ± epsilonbirlikte epsilon>0sağlanan tam olarak 100.00 alma şansı ..0 son derece düşük.)

Aynı şeyi eksi işareti ile de görebilirsiniz,

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

ve (un) kadar şaşırmış olmalısınız.

Öte yandan, int(-1/.01)önce bölümü gerçekleştirir ve sonra kat değil, 0'a doğru bir kesilmeint() sayısını uygular ! yani bu durumda,

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

dolayısıyla,

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

Yuvarlama, size bu operatör için SİZİN beklenen sonucu verecektir, çünkü yine, bu rakamlar için hata küçüktür.

(*) Olasılığın aynı olduğunu söylemiyorum, sadece yüzer aritmetik ile böyle bir hesaplama yaptığınızda bir a priori'nin, elde ettiğiniz şeyin bir tahmini olduğunu söylüyorum.


7

Kayan nokta sayıları çoğu ondalık sayıyı tam olarak temsil edemez, bu nedenle bir kayan nokta değişmezi yazdığınızda, gerçekte bu değişmez değerin yaklaşık bir değerini alırsınız. Yaklaşık değer, yazdığınız sayıdan daha büyük veya daha küçük olabilir.

Ondalık veya Kesir değerine çevirerek kayan nokta sayısının tam değerini görebilirsiniz.

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

Kesin olmayan değişmezimizin neden olduğu hatayı bulmak için Kesir türünü kullanabiliriz.

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

Numpy'den nextafter kullanarak 100 civarında granüler çift kesinlikli kayar nokta sayılarının ne olduğunu öğrenebiliriz.

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

Buradan, en yakın kayan nokta sayısının 1/0.01000000000000000020816681711721685132943093776702880859375aslında tam olarak 100 olduğunu tahmin edebiliriz .

Arasındaki fark 1//0.01ve int(1/0.01)yuvarlanmasıdır. 1 // 0.01 kesin sonucu tek bir adımda bir sonraki tam sayıya yuvarlar. Böylece 99 sonucunu elde ediyoruz.

int (1 / 0.01) ise iki aşamada yuvarlar, önce sonucu en yakın çift kesinlikli kayan nokta numarasına (tam olarak 100 olan) yuvarlar, sonra kayan nokta sayısını bir sonraki tamsayıya (yani yine tam 100).


Buna sadece yuvarlama demek yanıltıcıdır. Buna kesilme veya sıfıra doğru yuvarlama adı verilmelidir : int(0.9) == 0veint(-0.9) == 0
maxy

Burada bahsettiğiniz ikili kayan nokta türleridir. (Ayrıca ondalık kayan nokta türleri de vardır.)
Stephen C

3

Aşağıdakileri yürütürseniz

from decimal import *

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

Çıktı şöyle olacaktır:

99.99999999999999791833182883

Bu aşağı yuvarlama yüzden içten, temsil nasıl olduğunu //verecektir99


2
Bu durumda hatayı gösterecek kadar doğrudur, ancak "Ondalık" aritmetiğin de tam olmadığını unutmayın.
plugwash

İle Decimal(0.01)geç kaldın, hata zaten aramadan önce süzüldü Decimal. Bunun soruya nasıl bir cevap olduğundan emin değilim ... Önce cevabımda Decimal(1) / Decimal(100)gösterdiğim gibi, kesin bir 0.01 hesaplamalısınız .
trincot

@trincot Cevabım "Neden 1 // 0.01 == 99" başlıklı soruya OP'ye kayan sayıların dahili olarak nasıl ele alındığını göstermeye çalıştım.
Yağmur
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.