Python 3'te kayan nokta değeri 4 * 0,1 neden güzel görünüyor, ancak 3 * 0,1 değil?


158

Ondalık sayıların çoğunun tam bir kayan nokta temsili olmadığını biliyorum ( Kayan nokta matematik bozuk mu? ).

Ama neden her iki değerin gerçekten çirkin ondalık temsili olduğunda 4*0.1güzel yazdırıldığını görmüyorum 0.4, ama 3*0.1değil:

>>> 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')

7
Çünkü bazı sayılar tam olarak temsil edilebilir ve bazıları sayılmaz.
Morgan Thrapp

58
@MorganThrapp: hayır değil. OP, oldukça keyfi görünen biçimlendirme seçimini soruyor. Ne 0.3 ne de 0.4 tam olarak ikili kayan noktada gösterilemez.
Bathsheba

42
@BartoszKP: Python neden görüntülediği açıklamıyor, belge birkaç kez okumak zorunda 0.3000000000000000444089209850062616169452667236328125olduğu 0.30000000000000004ve 0.40000000000000002220446049250313080847263336181640625yanı .4soruya cevap vermez böylece aynı doğruluk var görünüyor olsa bile, vb.
Mooing Duck

6
Ayrıca bkz. Stackoverflow.com/questions/28935257/… - Bir kopya olarak kapatıldığından biraz rahatsız oldum ama bu olmadı.
Random832

12
Yeniden açıldı, lütfen "kayan nokta matematik kırıldı" kopyası olarak bunu kapatmayın .
Antti Haapala

Yanıtlar:


301

Bunun basit cevabı, 3*0.1 != 0.3nicemleme (yuvarlama) hatasından kaynaklanmaktadır (oysa 4*0.1 == 0.4iki güçle çarpmak genellikle "kesin" bir işlemdir).

.hexPython'daki yöntemi, bir sayının dahili temsilini görüntülemek için kullanabilirsiniz (temel olarak, taban-10 yaklaşımı yerine tam ikili kayan nokta değeri). Bu, kaputun altında neler olup bittiğini açıklamaya yardımcı olabilir.

>>> (0.1).hex()
'0x1.999999999999ap-4'
>>> (0.3).hex()
'0x1.3333333333333p-2'
>>> (0.1*3).hex()
'0x1.3333333333334p-2'
>>> (0.4).hex()
'0x1.999999999999ap-2'
>>> (0.1*4).hex()
'0x1.999999999999ap-2'

0.1, 0x1.999999999999a çarpı 2 ^ -4'tür. Sondaki "a", 10 rakamı anlamına gelir - diğer bir deyişle, ikili kayan noktadaki 0,1, 0,1 "tam" değerinden çok az daha büyüktür (çünkü nihai 0x0.99, 0x0.a'ya yuvarlanır). Bunu 4 ile çarptığınızda, üs değeri yukarı kayar (2 ^ -4'ten 2 ^ -2'ye), ancak sayı başka şekilde değişmez, bu nedenle 4*0.1 == 0.4.

Bununla birlikte, 3 ile çarptığınızda, 0x0.99 ve 0x0.a0 (0x0.07) arasındaki küçük küçük fark, son konumda tek basamaklı bir hata olarak görülen 0x0.15 hatasına dönüşür. Bu 0,1 * 3'ün yuvarlanmış 0,3 değerinden çok az daha büyük olmasına neden olur .

Python 3'ün şamandıra yuvarlak takılabilirrepr şekilde tasarlanmıştır , yani gösterilen değer tam olarak orijinal değere dönüştürülebilir olmalıdır. Bu nedenle, gösteremez ve aynı şekilde, ya da iki farklı sayılar yuvarlak açma sonra aynı sona ereceğini. Sonuç olarak, Python 3'ün motoru hafif bir hata ile birini göstermeyi seçer.0.30.1*3repr


25
Bu inanılmaz kapsamlı bir cevap, teşekkürler. (Özellikle, gösterdiğiniz için teşekkürler .hex(); var olduğunu bilmiyordum.)
NPE

21
@supercat: Python , ne olursa olsun , istenen değere yuvarlanacak en kısa dizeyi bulmaya çalışır . Açıkçası, değerlendirilen değer 0,5 dalga içinde olmalıdır (veya başka bir şeye yuvarlanır), ancak belirsiz durumlarda daha fazla basamak gerektirebilir. Kod çok gariptir, ancak bir göz atmak istiyorsanız: hg.python.org/cpython/file/03f2c8fc24ea/Python/dtoa.c#l2345
nneonneo

2
@supercat: Her zaman 0,5 ulp içindeki en kısa dize. ( Kesinlikle tek LSB'li bir şamandıraya bakıyorsak; yani, yuvarlak bağlarla eşitliği sağlayan en kısa dize). Bunun istisnaları bir hatadır ve bildirilmelidir.
Mark Dickinson

7
@MarkRansom eŞimdiden onaltılık bir rakam olduğu için başka bir şey kullandılar . Belki üs yerine güçp için .
Bergi

11
@Bergi: pBu bağlamda kullanımı (en azından) C99'a kadar gider ve ayrıca IEEE 754 ve diğer çeşitli dillerde (Java dahil) görünür. Ne zaman float.hexve float.fromhexuygulandığımda (benim tarafımdan :-), Python sadece o zamana kadar kurulan uygulamayı kopyaladı. Niyetin "Güç" için "p" olup olmadığını bilmiyorum, ama bunu düşünmenin güzel bir yolu gibi görünüyor.
Mark Dickinson

75

repr(ve strPython 3'te) değeri açık hale getirmek için gerektiği kadar basamak basacaktır. Bu durumda çarpmanın sonucu 3*0.10,3'e en yakın değer değildir (onaltılık 0x1.3333333333333p-2), aslında bir LSB daha yüksektir (0x1.3333333333334p-2), bu nedenle 0.3'ten ayırt etmek için daha fazla basamağa ihtiyacı vardır.

Diğer taraftan, çarpma 4*0.1 yapar herhangi bir ek basamak gerek yoktur, böylece, 0.4 (0x1.999999999999ap-2 hex) en yakın bir değer elde.

Bunu kolayca doğrulayabilirsiniz:

>>> 3*0.1 == 0.3
False
>>> 4*0.1 == 0.4
True

Yukarıdaki hex gösterimini kullandım çünkü güzel ve kompakt ve iki değer arasındaki bit farkını gösteriyor. Bunu kullanarak örneğin kendiniz yapabilirsiniz (3*0.1).hex(). Onları ondalık görkemiyle görmeyi tercih ederseniz, işte başlıyoruz:

>>> Decimal(3*0.1)
Decimal('0.3000000000000000444089209850062616169452667236328125')
>>> Decimal(0.3)
Decimal('0.299999999999999988897769753748434595763683319091796875')
>>> Decimal(4*0.1)
Decimal('0.40000000000000002220446049250313080847263336181640625')
>>> Decimal(0.4)
Decimal('0.40000000000000002220446049250313080847263336181640625')

2
(+1) Güzel cevap, teşekkürler. Bunun sonucunu dahil ederek "değil en yakın değer" noktasını gösteren değerinde olabilir mi 3*0.1 == 0.3ve 4*0.1 == 0.4?
NPE

@ NPE Bunu kapıdan hemen yapmalıydım, öneri için teşekkürler.
Mark Ransom

Bir çok insan kayan nokta onaltısını okuyamadığından, en yakın "çift" in 0,1, 0,3 ve 0,4'ün kesin ondalık değerlerini belirtmeye değip değmeyeceğini merak ediyorum.
supercat

@supercat iyi bir noktaya değindin. Bu süper büyük çiftleri metne koymak dikkat dağıtıcı olurdu, ama onları eklemenin bir yolunu düşündüm.
Mark Ransom

25

İşte diğer cevaplardan basitleştirilmiş bir sonuç.

Python'un komut satırında bir şamandırayı kontrol ederseniz veya yazdırırsanız repr, dize temsilini oluşturan işlevden geçer .

Sürüm 3.2'den başlayarak, Python'lar strve reprmümkünse hoş görünümlü ondalıkları tercih eden karmaşık bir yuvarlama şeması kullanın, ancak şamandıralar ve dize gösterimleri arasında iki yönlü (bire bir) eşlemeyi garanti etmek için gerektiğinde daha fazla basamak kullanır.

Bu şema repr(float(s)), tam olarak şamandıra olarak temsil edilemeseler bile, basit ondalık sayılar için güzel görünmenin değerini garanti eder (ör. Ne zaman s = "0.1").

Aynı zamanda float(repr(x)) == xher şamandıra içinx


2
Cevabınız Python> = 3.2 için doğrudur strve reprşamandıralar için aynıdır. Python 2.7 için, reprtanımladığınız özelliklere sahiptir, ancak strçok daha basittir - sadece 12 önemli haneyi hesaplar ve bunlara dayalı bir çıktı dizesi üretir. Python <= 2.6 için, her ikisi de reprve strsabit sayıda önemli basamağa dayanır (17 için repr, 12 için str). (Ve hiç kimse Python 3.0 veya Python 3.1'i umursamıyor :-)
Mark Dickinson

Teşekkürler @MarkDickinson! Cevabınıza yorumunuzu ekledim.
Aivar

2
Kabuktan yuvarlamanın reprPython 2.7 davranışının aynı olacağını unutmayın ...
Antti Haapala

5

Python'un uygulamasına gerçekten özgü değildir, ancak ondalık dizge işlevlerine herhangi bir kayan noktalı sayıya uygulanmalıdır.

Bir kayan nokta sayısı temelde bir ikili sayıdır, ancak bilimsel gösterimlerde önemli rakamların sabit bir sınırı vardır.

Tabanla paylaşılmayan asal sayı faktörüne sahip herhangi bir sayının tersi her zaman yinelenen nokta noktası gösterimi ile sonuçlanır. Örneğin 1/7, 10 ile paylaşılmayan ve bu nedenle tekrarlayan bir ondalık gösterime sahip olan bir ana faktöre (7) sahiptir ve aynı, 2 ve 5 numaralı birincil faktörlerle 1/10 için geçerlidir, ikincisi 2 ile paylaşılmaz ; yani 0.1 nokta noktasından sonra sonlu sayıda bit ile tam olarak temsil edilemez.

0.1'in tam bir temsili olmadığından, yaklaşımı ondalık nokta dizesine dönüştüren bir işlev genellikle belirli değerleri yaklaşık olarak göstermeye çalışır, böylece 0.1000000000004121 gibi sezgisel olmayan sonuçlar elde etmezler.

Kayan nokta bilimsel gösterimde olduğu için, tabanın gücü ile çarpma, sayının sadece üssü kısmını etkiler. Örneğin, ondalık gösterim için 1.231e + 2 * 100 = 1.231e + 4 ve ikili gösterimde 1.00101010e11 * 100 = 1.00101010e101. Tabanın gücü olmayan bir güçle çarparsam, önemli basamaklar da etkilenir. Örneğin, 1.2e1 * 3 = 3.6e1

Kullanılan algoritmaya bağlı olarak, yalnızca önemli rakamlara dayanarak ortak ondalık sayıları tahmin etmeye çalışabilir. Hem 0,1 hem de 0,4 ikili değerlerde aynı anlamlı rakamlara sahiptir, çünkü bunların şamandıraları esasen sırasıyla (8/5) (2 ^ -4) ve (8/5) (2 ^ -6) kesmeleridir . Algoritma 8/5 sigfig desenini ondalık 1.6 olarak tanımlarsa, 0.1, 0.2, 0.4, 0.8 vb. Üzerinde çalışacaktır. Ayrıca, float 3 gibi float 10'un bölünmesi gibi diğer kombinasyonlar için sihirli sigfig desenleri olabilir. ve istatistiksel olarak 10'a bölünerek oluşması muhtemel diğer sihirli desenler.

3 * 0.1 durumunda, son birkaç önemli rakam muhtemelen bir şamandırayı 3 şamandıra 10'a bölmekten farklı olacaktır, bu da algoritmanın hassas kayıp toleransına bağlı olarak 0.3 sabiti için sihirli sayıyı tanımamasına neden olur.

Düzenleme: https://docs.python.org/3.1/tutorial/floatingpoint.html

İlginçtir, en yakın yaklaşık ikili kesiri paylaşan birçok farklı ondalık sayı vardır. Örneğin, 0.1 ve 0.10000000000000001 ve 0.1000000000000000055511151231257827021181583404541015625 sayılarının hepsi 3602879701896397/2 ** 55 ile yakındır. ) == x.

Hassasiyet kaybına tolerans yoktur, eğer float x (0.3) float y (0.1 * 3) ile tam olarak eşit değilse, repr (x) repr (y) ile tam olarak eşit değildir.


4
Bu mevcut cevaplara pek bir şey katmıyor.
Antti Haapala

1
"Kullanılan algoritmaya bağlı olarak, yalnızca önemli rakamlara dayanarak ortak ondalık sayıları tahmin etmeye çalışabilir." <- Bu saf spekülasyon gibi görünüyor. Diğer cevaplar Python'un gerçekte ne yaptığını açıkladı .
Mark Dickinson
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.