Aracılığıyla paylaş


Kayan nokta hesaplamalarında duyarlık ve doğruluk

Özgün KB numarası: 125056

Özet

Kayan nokta hesaplamalarındaki duyarlık, yuvarlama ve doğruluğun programcı için şaşırtıcı sonuçlar üretmek için çalışabileceği birçok durum vardır. Dört genel kurala uymalıdır:

  1. Hem tek hem de çift duyarlık içeren bir hesaplamada sonuç genellikle tek duyarlıktan daha doğru olmaz. Çift duyarlık gerekiyorsa, sabitler dahil olmak üzere hesaplamadaki tüm terimlerin çift duyarlıklı olarak belirtildiğinden emin olun.

  2. Basit bir sayısal değerin bilgisayarda doğru bir şekilde temsil ettiğini asla varsaymayın. Kayan nokta değerlerinin çoğu kesin olarak sonlu ikili değer olarak temsil edilemez. Örneğin, .1.0001100110011... ikilidir (sonsuza kadar yineler), bu nedenle tüm bilgisayarları içeren ikili aritmetik kullanılarak bir bilgisayarda tam doğrulukla temsil edilemez.

  3. Sonucun son ondalık basamak için doğru olduğunu asla varsaymayın. "Doğru" yanıt ile herhangi bir kayan nokta işleme biriminin sonlu duyarlığıyla hesaplanabilecekler arasında her zaman küçük farklılıklar vardır.

  4. İki kayan nokta değerini hiçbir zaman karşılaştırmayın ve eşit olup olmadıklarını görün. Bu, 3. kuralın bir kaydıdır. Sayılar arasında neredeyse her zaman eşit olması gereken küçük farklılıklar olacaktır. Bunun yerine her zaman sayıların neredeyse eşit olup olmadığını denetleyin. Başka bir deyişle, aralarındaki farkın küçük mü yoksa önemsiz mi olduğunu denetleyin.

Daha Fazla Bilgi

Genel olarak, yukarıda açıklanan kurallar C, C++ ve assembler gibi tüm diller için geçerlidir. Aşağıdaki örneklerde FORTRAN PowerStation kullanan bazı kurallar gösterilmektedir. Örneklerin tümü, C dilinde yazılan son örnek dışında herhangi bir seçenek olmadan FORTRAN PowerStation 32 kullanılarak derlenmiştir.

Örnek 1

İlk örnekte iki şey gösterilmektedir:

  • FORTRAN sabitlerinin varsayılan olarak tek duyarlıklı olması (C sabitleri varsayılan olarak çift duyarlıktır).
  • Tek duyarlık terimleri içeren hesaplamalar, tüm terimlerin tek duyarlıklı olduğu hesaplamalardan çok daha doğru değildir.

1.1 (tek duyarlık sabiti) ile başlatıldıktan sonra, y tek bir duyarlık değişkeni kadar yanlıştır.

x = 1.100000000000000  y = 1.100000023841858

Tek bir duyarlık değerini doğru bir çift duyarlık değeriyle çarpmanın sonucu, iki tek duyarlık değerini çarpmak kadar kötüdür. Her iki hesaplamada da iki çift duyarlık değerinin çarpımının binlerce katı hata vardır.

true = 1.320000000000000 (multiplying 2 double precision values)
y    = 1.320000052452087 (multiplying a double and a single)
z    = 1.320000081062318 (multiplying 2 single precision values)

Örnek kod

C Compile options: none

       real*8 x,y,z
       x = 1.1D0
       y = 1.1
       print *, 'x =',x, 'y =', y
       y = 1.2 * x
       z = 1.2 * 1.1
       print *, x, y, z
       end

Örnek 2

Örnek 2 ikinci dereceden denklemi kullanır. Çift duyarlıklı hesaplamaların bile mükemmel olmadığını ve küçük hataların kesin sonuçlara sahip olup olmamasına bağlı olmadan önce hesaplamanın sonucunun test edilmesi gerektiğini gösterir. Örnek 2'deki karekök işlevine giriş yalnızca biraz negatiftir, ancak yine de geçersizdir. Çift duyarlıklı hesaplamalarda küçük hatalar olmasaydı sonuç şöyle olurdu:

Root =   -1.1500000000

Bunun yerine aşağıdaki hatayı oluşturur:

çalışma zamanı hatası M6201: MATH

  • sqrt: ETKİ ALANI hatası

Örnek kod

C Compile options: none

       real*8 a,b,c,x,y
       a=1.0D0
       b=2.3D0
       c=1.322D0
       x = b**2
       y = 4*a*c
       print *,x,y,x-y
       print "(' Root =',F16.10)",(-b+dsqrt(x-y))/(2*a)
       end

Örnek 3

Örnek 3, iyileştirme açık olmasa bile gerçekleşen iyileştirmeler nedeniyle değerlerin geçici olarak beklenenden daha yüksek bir duyarlığı koruyabileceğini ve eşitlik için iki kayan nokta değerini test etmek akıllıca olmadığını gösterir.

Bu örnekte iki değer eşittir ve eşit değildir. İlk IF'de, Z değeri hala yardımcı işlemcinin yığınındadır ve Y ile aynı duyarlıktadır. Bu nedenle X, Y'ye eşit değildir ve ilk ileti yazdırılır. İkinci EĞER sırasında, Z'nin bellekten yüklenmesi gerekiyordu ve bu nedenle X ile aynı duyarlık ve değere sahipti ve ikinci ileti de yazdırılır.

Örnek kod

C Compile options: none

       real*8 y
       y=27.1024D0
       x=27.1024
       z=y
       if (x.ne.z) then
         print *,'X does not equal Z'
       end if
       if (x.eq.z) then
         print *,'X equals Z'
       end if
       end

Örnek 4

Örnek kod 4'ün ilk bölümü, 1,0'a yakın iki sayı arasındaki olası en küçük farkı hesaplar. Bunu, 1.0 ikili gösterimine tek bir bit ekleyerek yapar.

x   = 1.00000000000000000  (one bit more than 1.0)
y   = 1.00000000000000000  (exactly 1.0)
x-y =  .00000000000000022  (smallest possible difference)

FORTRAN'ın bazı sürümleri, sayıları görüntülerken yuvarlar, böylece doğal sayısal kesinlik çok belirgin olmaz. Bu nedenle, x ve y görüntülendiğinde aynı görünür.

Örnek kod 4'ün ikinci bölümü, 10,0'a yakın iki sayı arasındaki olası en küçük farkı hesaplar. Bunu da 10.0 ikili gösterimine tek bir bit ekleyerek yapar. 10'a yakın sayılar arasındaki farkın 1'e yakın olan farktan daha büyük olduğuna dikkat edin. Bu, bir sayının mutlak değeri ne kadar büyük olursa, belirli bir sayıda bitte o kadar az hassas bir şekilde depolanabileceğini gösteren genel ilkeyi gösterir.

x   = 10.00000000000000000  (one bit more than 10.0)
y   = 10.00000000000000000  (exactly 10.0)
x-y =   .00000000000000178

Bu sayıların ikili gösterimi de yalnızca 1 bit farklı olduğunu göstermek için görüntülenir.

x = 4024000000000001 Hex
y = 4024000000000000 Hex

Örnek kod 4'ün son bölümü, basit yinelenen olmayan ondalık değerlerin genellikle ikili olarak yalnızca yinelenen bir kesirle temsil edilebileceğini gösterir. Bu durumda yinelenen bir CCCCCCCCCC faktörü gerektiren x=1.05... Mantiste (Onaltılık). FORTRAN'da, mümkün olan en yüksek doğruluğu korumak için son basamak "C" yukarı "D" olarak yuvarlandı:

x = 3FF0CCCCCCCCCCCD (Hex representation of 1.05D0)

Yuvarlamadan sonra bile sonuç tam olarak doğru değildir. En az önemli rakamdan sonra bir hata vardır ve ilk rakamı kaldırarak bunu görebiliriz.

x-1 = .05000000000000004

Örnek kod

C Compile options: none

       IMPLICIT real*8 (A-Z)
       integer*4 i(2)
       real*8 x,y
       equivalence (i(1),x)

       x=1.
       y=x
       i(1)=i(1)+1
       print "(1x,'x  =',F20.17,'  y=',f20.17)", x,y
       print "(1x,'x-y=',F20.17)", x-y
       print *

       x=10.
       y=x
       i(1)=i(1)+1
       print "(1x,'x  =',F20.17,'  y=',f20.17)", x,y
       print "(1x,'x-y=',F20.17)", x-y
       print *
       print "(1x,'x  =',Z16,' Hex  y=',Z16,' Hex')", x,y
       print *

       x=1.05D0
       print "(1x,'x  =',F20.17)", x
       print "(1x,'x  =',Z16,' Hex')", x
       x=x-1
       print "(1x,'x-1=',F20.17)", x
       print *
       end

Örnek 5

C'de kayan sabitler varsayılan olarak çifttir. "89,95f" gibi bir kayan değer belirtmek için "f" kullanın.

/* Compile options needed: none
*/ 

#include <stdio.h>

void main()
   {
      float floatvar;
      double doublevar;

/* Print double constant. */ 
      printf("89.95 = %f\n", 89.95);      // 89.95 = 89.950000

/* Printf float constant */ 
      printf("89.95 = %f\n", 89.95F);     // 89.95 = 89.949997

/*** Use double constant. ***/ 
      floatvar = 89.95;
      doublevar = 89.95;

printf("89.95 = %f\n", floatvar);   // 89.95 = 89.949997
      printf("89.95 = %lf\n", doublevar); // 89.95 = 89.950000

/*** Use float constant. ***/ 
      floatvar = 89.95f;
      doublevar = 89.95f;

printf("89.95 = %f\n", floatvar);   // 89.95 = 89.949997
      printf("89.95 = %lf\n", doublevar); // 89.95 = 89.949997
   }