2012-11-27 24 views
6

Bu java'nın çift hassasiyetli tuzaklara sahip olduğunu biliyorum, ama bazen nedense, yaklaşık sonuç bazen iyi değil. BöyleNeden çift artı bazen doğru, bazen yanlış?

kodu: Böyle

for (float value = 0.0f; value < 1.0f; value += 0.1f) 
    System.out.println(value); 

sonucu: Eğer devlet gibi

0.0 
0.1 
0.2 
0.3 
... 
0.70000005 
0.8000001 
0.9000001 
+0

Kullanım yanı ilk birkaç durumda doğruluğundan yerine şamandıranın iki katına. –

cevap

21

, tüm sayılar tam olarak ieee754 temsil edilebilir. Java'nın bu sayıları yazdırmak için kullandığı kurallarla bağlantılı olarak, göreceğiniz şeyi etkiler.

Arka plan için, IEEE754 hatalarını kısaca ele alacağım. Bu özel durumda, 0.1 tam olarak temsil edilemez, böylece kullanılan gerçek sayının 0.100000001490116119384765625 gibi bir şey olduğunu görürsünüz. Bunun neden böyle olduğunu görmek için here numaralı telefona bakın.

. "Hatalı" değerleri almanın nedeni, bu hatanın (0.000000001490116119384765625) aşamalı olarak eklenmesidir.


0.1 veya 0.2 (veya benzer sayılar) hep öyle değil sebebi o hata Java baskı kod yerine gerçek değeri kendisi ile ilgisi var göstermektedir.

0.1 aslında beklediğinizden biraz daha yüksek olsa da, yazdırılan kod size tüm rakamları vermez. Format dizesini, ondalıktan sonra 50 hane yayınlayacak şekilde ayarlarsanız, gerçek değeri göreceğinizi görürsünüz.

Java'nın bir şamandırayı (açık biçimlendirme olmadan) yazdırmaya nasıl karar verdiğine ilişkin kurallar here ayrıntılı olarak açıklanmıştır. basamaklı sayım için ilgili biraz:

olmalı en az bir rakam kesirli kısmı temsil ve benzersiz argüman değerini ayırt etmek gereklidir kadar o kadar çok, ama sadece gibi birçok, daha basamaktan fazla tip floatın bitişik değerlerinden.

Örnek olarak

, burada bu nasıl çalıştığını gösteren bazı kod:

public class testprog { 
    public static void main (String s[]) { 
     float n; int i, x; 
     for (i = 0, n = 0.0f; i < 10; i++, n += 0.1f) { 
      System.out.print(String.format("%30.29f %08x ", 
       n, Float.floatToRawIntBits(n))); 
      System.out.println (n); 
     } 
    } 
} 

bu çıktısı:

0.00000000000000000000000000000 00000000 0.0 
0.10000000149011611938476562500 3dcccccd 0.1 
0.20000000298023223876953125000 3e4ccccd 0.2 
0.30000001192092895507812500000 3e99999a 0.3 
0.40000000596046447753906250000 3ecccccd 0.4 
0.50000000000000000000000000000 3f000000 0.5 
0.60000002384185791015625000000 3f19999a 0.6 
0.70000004768371582031250000000 3f333334 0.70000005 
0.80000007152557373046875000000 3f4cccce 0.8000001 
0.90000009536743164062500000000 3f666668 0.9000001 

İlk sütun içinde gerçek değerdir IEEE754 sınırlamalarından gelen hatalar dahil olmak üzere, şamandıra.

İkinci sütun, kayan nokta değerinin (gerçek tamsayı değerinden ziyade bellekte nasıl göründüğü) 32 bitlik tamsayı gösterimi olup, düşük düzeyli bit temsilindeki değerleri denetlemek için kullanışlıdır.

Son sütun, biçimlendirme olmadan sayıyı yazdırırken gördüğünüz şeydir.


Şimdi size gösterecektir biraz daha kod bakarak hem çevredeki değerlerle farklılıklar basılır kontrolleri ne kadar sürekli değişken zamanlı değer katmanın yanlışlıklar size yanlış numara ve kaç para verir:

public class testprog { 
    public static void outLines (float n) { 
     int i, val = Float.floatToRawIntBits(n); 
     for (i = -1; i < 2; i++) { 
      n = Float.intBitsToFloat(val+i); 
      System.out.print(String.format("%30.29f %.08f %08x ", 
       n, n, Float.floatToRawIntBits(n))); 
      System.out.println (n); 
     } 
     System.out.println(); 
    } 
    public static void main (String s[]) { 
     float n = 0.0f; 
     for (int i = 0; i < 6; i++) n += 0.1f; 
     outLines (n); n += 0.1f; 
     outLines (n); n += 0.1f; 
     outLines (n); n += 0.1f; 
     outLines (0.7f); 
    } 
} 

Bu kod, bu ve bitişik yüzer değerlerini yazdırır sonra 0.6 kalkmaya 0.1 sürekli ilave kullanır. bu çıktısı: bakmak

0.59999996423721310000000000000 0.59999996 3f199999 0.59999996 
0.60000002384185790000000000000 0.60000002 3f19999a 0.6 
0.60000008344650270000000000000 0.60000008 3f19999b 0.6000001 

0.69999998807907100000000000000 0.69999999 3f333333 0.7 
0.70000004768371580000000000000 0.70000005 3f333334 0.70000005 
0.70000010728836060000000000000 0.70000011 3f333335 0.7000001 

0.80000001192092900000000000000 0.80000001 3f4ccccd 0.8 
0.80000007152557370000000000000 0.80000007 3f4cccce 0.8000001 
0.80000013113021850000000000000 0.80000013 3f4ccccf 0.80000013 

0.69999992847442630000000000000 0.69999993 3f333332 0.6999999 
0.69999998807907100000000000000 0.69999999 3f333333 0.7 
0.70000004768371580000000000000 0.70000005 3f333334 0.70000005 

ilk şey söz Java baskı özelliklerine göre (son sütun çevreleyen hatları ile ayırt her bloğun orta sıralarda yeterli fraksiyonel basamak olmasıdır Önceden). Yalnızca ondalık sonra üç yeri olsaydı

Örneğin, sen 0.6 ve 0.6000001 ayırt etmek mümkün olmaz (bitişik bit desenleri 0x3f19999a ve 0x3f19999b). Yani, o kadar çok ihtiyacı olarak yazdırır. Fark edeceğiniz

ikinci şey ikinci blokta bizim 0.7 değeri değil0.7 olmasıdır. Daha ziyade, (önceki satırda) o numaraya bir daha da yakın bit deseni var aslında rağmen 0.70000005bu.

Bu 0.1 ekleyerek neden hataların kademeli birikmesinin neden olmuştur. Sen sadece sürekli 0.1 ekleyerek yerine doğrudan 0.7 kullanılırsa, doğru değer elde ediyorum, bu son bloktan görebilirsiniz.

Özel durumunuzda, bu sorunlara neden olan numaralı sorunudur. Java (sahip olduğu) yeterince yakın bir tahminini var değil çünkü 0.70000005 çıktısı alıyoruz gerçeği değil, çünkü ilk etapta 0.7 lazım yol var.

Içermesi yukarıdaki bu kodu değiştirirseniz:

outLines (0.1f); 
outLines (0.2f); 
outLines (0.3f); 
outLines (0.4f); 
outLines (0.5f); 
outLines (0.6f); 
outLines (0.7f); 
outLines (0.8f); 
outLines (0.9f); 

Eğer doğru o gruptaki tüm numaraları yazdırabilirsiniz bulacaksınız.

İlgili konular