0,1 kat eklemek neden kayıpsız kalır?


152

0.1Ondalık sayının sonlu bir ikili sayı ile tam olarak temsil edilemeyeceğini biliyorum ( açıklama ), bu yüzden double n = 0.1biraz hassasiyet kaybedecek ve tam olarak olmayacak 0.1. Öte yandan 0.5tam olarak olduğu için temsil edilebilir 0.5 = 1/2 = 0.1b.

0.1 Üç kez eklemenin tam olarak 0.3aşağıdaki kodun yazdırılmasının sağlanmayacağı anlaşılırsa false:

double sum = 0, d = 0.1;
for (int i = 0; i < 3; i++)
    sum += d;
System.out.println(sum == 0.3); // Prints false, OK

Ama sonra 0.1 beş kez eklemenin tam olarak 0.5nasıl vereceği ? Aşağıdaki kod yazdırılır true:

double sum = 0, d = 0.1;
for (int i = 0; i < 5; i++)
    sum += d;
System.out.println(sum == 0.5); // Prints true, WHY?

Eğer 0.1tam olarak temsil edilemiyor, nasıl o 5 kez ekleyerek tam olarak sağlamasıdır 0.5tam olarak ifade edilebilen?


7
Gerçekten araştırırsanız emin olabilirsiniz, ancak kayan nokta "sürprizlerle" yüklenir ve bazen sadece şaşkınlığa bakmak daha iyidir.
Hot Licks

3
Bunu mathy tarzında düşünüyorsunuz. Kayan nokta aritmetiği hiçbir şekilde matematik değildir.
Jakob

13
@HotLicks çok yanlış bir tutum var.
Ocak

2
@RussellBorogove, optimize edilmiş olsa bile, yalnızca sumdöngü gerçekten yürütülmüş gibi aynı nihai değere sahip olsaydı geçerli bir optimizasyon olurdu . C ++ standardında buna "if-kuralı" veya "aynı gözlemlenebilir davranış" denir.
Ocak

7
@ Jakob hiç doğru değil. Kayan nokta aritmetiği, hata sınırlarının iyi matematiksel tedavisi ve benzeri ile titizlikle tanımlanır. Sadece birçok programcı ya analizleri takip etmek istemiyor ya da yanlışlıkla "kayan nokta kesin değil" in bilinmesi gereken tek şey olduğuna ve analizin uğraşmaya değmeyeceğine inanıyorlar.
Ocak

Yanıtlar:


155

Yuvarlama hatası rastgele değildir ve uygulanma şekli hatayı en aza indirmeye çalışır. Bu, bazen hatanın görünmediği veya hata olmadığı anlamına gelir.

Örnek için 0.1tam değildir 0.1yani new BigDecimal("0.1") < new BigDecimal(0.1)ama 0.5tam olarak1.0/2

Bu program size gerçek değerleri gösterir.

BigDecimal _0_1 = new BigDecimal(0.1);
BigDecimal x = _0_1;
for(int i = 1; i <= 10; i ++) {
    System.out.println(i+" x 0.1 is "+x+", as double "+x.doubleValue());
    x = x.add(_0_1);
}

baskılar

0.1000000000000000055511151231257827021181583404541015625, as double 0.1
0.2000000000000000111022302462515654042363166809082031250, as double 0.2
0.3000000000000000166533453693773481063544750213623046875, as double 0.30000000000000004
0.4000000000000000222044604925031308084726333618164062500, as double 0.4
0.5000000000000000277555756156289135105907917022705078125, as double 0.5
0.6000000000000000333066907387546962127089500427246093750, as double 0.6000000000000001
0.7000000000000000388578058618804789148271083831787109375, as double 0.7000000000000001
0.8000000000000000444089209850062616169452667236328125000, as double 0.8
0.9000000000000000499600361081320443190634250640869140625, as double 0.9
1.0000000000000000555111512312578270211815834045410156250, as double 1.0

Not: bu 0.3biraz kapalıdır, ancak 0.4bitlere ulaştığınızda 53 bit sınırına sığacak şekilde bir tane kaydırmanız gerekir ve hata atılır. Yine, bir hata sürünür için arkaya 0.6ve 0.7ancak için 0.8için 1.0hata atılır.

5 kez eklenmesi hatayı biriktirmeli, iptal etmemelidir.

Bir hatanın nedeni sınırlı hassasiyetten kaynaklanmaktadır. yani 53 bit. Bu, sayı büyüdükçe daha fazla bit kullandığından, bitlerin uçtan düşmesi gerektiği anlamına gelir. Bu, bu durumda sizin lehinize olan yuvarlamaya neden olur.
Daha küçük bir sayı alırken bunun tersi bir etki elde edebilirsiniz, örneğin 0.1-0.0999=> 1.0000000000000286E-4 ve öncekinden daha fazla hata görürsünüz.

Java 6'da Math.round (0.49999999999999994) neden geri dönüyor ?


1
Bu nereye uygulanır?
EpicPandaForce

16
@Zhuinden CPU IEEE-754 standardını takip eder. Java, temel CPU talimatlarına erişmenizi sağlar ve dahil olmaz. en.wikipedia.org/wiki/IEEE_floating_point
Peter Lawrey

10
@PeterLawrey: İşlemci olması gerekmez. CPU'da kayan nokta olmayan (ve kullanımda ayrı bir FPU olmayan) bir makinede, IEEE aritmetiği yazılım tarafından gerçekleştirilecektir. Ve ana CPU'nun kayan noktası varsa ancak IEEE gereksinimlerine uymuyorsa, bu CPU için bir Java uygulamasının yumuşak şamandıra kullanmak zorunda kalacağını düşünüyorum ...
R .. GitHub DURDURMAK ICE

1
@R .. Bu durumda strictfp sabit nokta tamsayılarını düşünmek için Zaman'ı kullanırsanız ne olacağını bilmiyorum . (veya BigDecimal)
Peter Lawrey

2
@eugene anahtar problem kayan noktanın temsil edebileceği sınırlı değerlerdir. Bu sınırlama, bilgi kaybına ve sayı arttıkça hata kaybına neden olabilir. Yuvarlama kullanır, ancak bu durumda yuvarlar, böylece 0.1 biraz fazla büyük olduğundan, biraz fazla büyük olan bir sayı doğru değere dönüşür. Tam 0.5
Peter Lawrey

47

Kayan nokta, taşma Engelleme, x + x + xtam olarak gerçek 3 * doğru yuvarlanır (yani en yakın) kayan nokta sayısı x, x + x + x + xtam olarak 4 * olduğu xve x + x + x + x + xtekrar 5 * için doğru bir şekilde yuvarlak kayan nokta yaklaşımdır x.

İlk sonuç, kesin x + x + xolan gerçeğinden kaynaklanmaktadır x + x. x + x + xdolayısıyla sadece bir yuvarlamanın sonucudur.

İkinci sonuç daha zordur, bunun bir gösterimi burada tartışılmaktadır (ve Stephen Canon, son 3 hanesinde vaka analizi ile başka bir kanıtla ilgilidir x). Özetlemek gerekirse, ya 3 * xaynı olan binade 2 * olarak xya da 4 * aynı binade olduğu x, ve her bir durumda, üçüncü sıra hata ikinci eklenmesi ile hata (iptal olduğunu anlamak mümkündür Daha önce de söylediğimiz gibi, ilk ekleme tamdır).

Üçüncü sonuç, “ x + x + x + x + xdoğru bir şekilde yuvarlanır”, ikincisinden birincinin kesinliğinden türetildiği gibi elde edilir x + x.


İkinci sonuç neden 0.1 + 0.1 + 0.1 + 0.1kayan nokta sayısının tam olarak olduğunu açıklar 0.4: 1/10 ve 4/10 rasyonel sayıları kayan noktaya dönüştürüldüğünde aynı göreceli hata ile aynı şekilde tahmin edilir. Bu kayan nokta sayıları, aralarında tam olarak 4 orana sahiptir. Birinci ve üçüncü sonucu göstermektedir 0.1 + 0.1 + 0.1ve 0.1 + 0.1 + 0.1 + 0.1 + 0.1kendi içinde, sadece, sırasıyla sonuçları ilgilidir naif hata analizi ile anlaşılabilir olabilir daha az hata sahip olması beklenmektedir, ancak olabilir 3 * 0.1ve 5 * 0.1yakın ama zorunlu olarak özdeş olması bekleniyor edilebilir, 0.3ve 0.5.

0.1Dördüncü eklemeden sonra eklemeye devam ederseniz, nihayet “ 0.1kendi kendine n kere eklendi” yi bir süreliğine ikili olarak ayıran yuvarlama hatalarını gözlemleyeceksiniz . Bundan sonra, emilim gerçekleşmeye başlar ve eğri düzleşir.n * 0.1 ve n / 10'dan daha da . Eğer n'nin bir fonksiyonu olarak “0.1 kere kendisine eklenen 0.1” değerlerini çizseydiniz, sürekli eğim çizgilerini on iki kez gözlemlersiniz (n. İlave işlemin sonucu belirli bir binade düşmeye mahkum olur olmaz, ilacın özelliklerinin, aynı binade sonuç veren önceki ilavelere benzer olması beklenebilir). Aynı binade içinde, hata ya büyüyecek ya da küçülecektir. Binade'den binade'ye kadar olan eğimlerin sırasına baksaydınız,0.1


1
İlk satırda x + x + x'in tam olarak doğru olduğunu söylüyorsunuz, ancak sorudaki örnekten değil.
Alboz

2
@Alboz Ben bunun gerçek 3 * 'e x + x + xtam olarak doğru yuvarlanmış kayan nokta sayısı olduğunu söylüyorum x. “Doğru yuvarlanmış”, bu bağlamda “en yakın” anlamına gelir.
Pascal Cuoq

4
+1 Bu kabul edilen cevap olmalıdır. Aslında sadece belirsiz genellemelerden ziyade neler olduğuna dair açıklamalar / kanıtlar sunuyor.
R .. GitHub BUZA YARDIMCI DURDUR

1
@Alboz (hepsi soru tarafından öngörülüyor). Ancak bu cevabın açıkladığı şey, hataların en kötü durumda toplanmak yerine tesadüfen nasıl iptal edildiğidir.
Ocak

1
@cbus 0.1, onaltılık (sonsuz bir basamak dizisi) 0x1.999999999999999999999… p-4'tür. 0x1.99999ap-4 olarak çift kesinlikte yaklaşık olarak tahmin edilir. 0.2, onaltılık olarak 0x1.999999999999999999999… p-3'tür. Aynı nedenden ötürü 0.1, 0x1.99999ap-4 olarak, 0.2 ise 0x1.99999ap-3 olarak tahmin edilmektedir. Bu arada, 0x1.99999ap-3 de tam olarak 0x1.99999ap-4 + 0x1.99999ap-4'tür.
Pascal Cuoq

-1

Kayan nokta sistemleri, yuvarlama için birkaç ekstra hassasiyet parçasına sahip olmak da dahil olmak üzere çeşitli büyü yapar. Böylece, 0,1'in hatalı gösterimi nedeniyle oluşan çok küçük hata, 0,5'e yuvarlanır.

Kayan noktayı sayıları temsil etmenin harika ama ETKİSİZ bir yol olarak düşünün. Olası tüm numaralar bir bilgisayarda kolayca gösterilmez. PI gibi irrasyonel sayılar. Veya SQRT (2) gibi. (Sembolik matematik sistemleri onları temsil edebilir, ama ben "kolayca" dedim.)

Kayan nokta değeri çok yakın olabilir, ancak kesin olmayabilir. Pluto'ya gidip milimetre uzakta olabileceğiniz kadar yakın olabilir. Ama yine de matematiksel anlamda kesin değil.

Yaklaşık olmak yerine tam olmanız gerektiğinde kayan nokta kullanmayın. Örneğin, muhasebe uygulamaları bir hesaptaki belirli sayıda peni tam olarak takip etmek ister. Tamsayılar bunun için iyidir çünkü kesinler. Tamsayılarla izlemeniz gereken birincil sorun taşmadır.

Para birimi için BigDecimal kullanımı iyi sonuç verir, çünkü temel temsil, tam da olsa bir tamsayıdır.

Kayan nokta sayılarının hatalı olduğunu kabul ederek, hala birçok kullanımları vardır. Navigasyon için koordinat sistemleri veya grafik sistemlerinde koordinatlar. Astronomik değerler. Bilimsel değerler. (Muhtemelen bir beyzbol kütlesini bir elektron kütlesi içinde tam olarak bilemezsiniz, bu nedenle hatalılık gerçekten önemli değildir.)

Uygulamaları saymak için (muhasebe dahil) tamsayı kullanın. Bir kapıdan geçen insan sayısını saymak için int veya long kullanın.


2
Soru [java] olarak etiketlendi. Java dil tanımlamasında “birkaç ekstra kesinlik biti” için herhangi bir hüküm yoktur , yalnızca birkaç ekstra üs biti için (ve yalnızca kullanmıyorsanız strictfp). Bir şeyi anlamaktan vazgeçmiş olmanız, onun anlaşılmaz olduğu veya başkalarının onu anlamak için vazgeçmesi gerektiği anlamına gelmez. Bkz stackoverflow.com/questions/18496560 Java uygulamaları (ile herhangi bir ekstra hassas bit hükümler ne de içermez dil tanımı uygulamak için gidecek uzunlukları bir örneği olarak strictfpherhangi bir ekstra exp biraz,)
Pascal Cuoq
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.