Tüm yazılar

#yazılım hataları#kayan nokta#javascript

Bilgisayar Neden 0,1 + 0,2'yi Hesaplayamıyor? Kayan Noktanın Gizli Dünyası

Tarayıcınızın konsoluna 0.1 + 0.2 yazın: sonuç 0.30000000000000004. Bu bir hata değil, bilgisayarların sayı tutma biçiminin doğal sonucu. Nedenini, bir füze savunma sistemine mal olan hikâyesini ve paranızı nasıl koruyacağınızı anlatıyoruz.

Bilgisayar Neden 0,1 + 0,2'yi Hesaplayamıyor? Kayan Noktanın Gizli Dünyası
İçindekiler 9

Tarayıcınızda F12'ye basın, konsolu açın ve şunu yazın:

0.1 + 0.2
// 0.30000000000000004

İlkokul matematiğiyle çözülen bir toplamayı, saniyede milyarlarca işlem yapan bir bilgisayar yanlış yapıyor. Üstelik bu JavaScript'e özgü değil: Python, Java, C#, C++ ve Excel'in arka planı aynı sonucu üretir. Bu bir hata değil; bilgisayarların sayı tutma biçiminin kaçınılmaz bir sonucu. Ve bu "küçük" fark, tarihte insan hayatına mal oldu.

Kısaca:

  • Bilgisayarlar sayıları ikili (2 tabanlı) sistemde tutar. 0,1 ikili sistemde tıpkı 1/3'ün onluk sistemdeki gibi sonsuza kadar tekrar eder.
  • Sonsuz basamak sonlu bir kutuya sığmadığı için yuvarlanır; 0,1 aslında 0,1000000000000000055... olarak saklanır.
  • 1991'de Patriot füze savunma sistemindeki benzer bir yuvarlama hatası, 28 askerin ölümüyle sonuçlanan bir saldırının önlenememesine yol açtı.
  • Parayla ilgili hesaplarda asla kayan noktalı sayı (float/double) kullanmayın; kuruş cinsinden tam sayı ya da ondalık veri türü kullanın.

1/3'ü ondalık yazmayı deneyin

Sorunu anlamanın en kolay yolu tanıdık bir örnek. 1/3'ü ondalık sayı olarak yazın: 0,3333333... Kaç basamak yazarsanız yazın bitmez. Bir yerde durup yuvarlamak zorundasınız. Sonra üç tane 0,3333'ü toplarsanız 1 değil, 0,9999 elde edersiniz.

Onluk sistemde bir kesrin sonlu yazılabilmesi için paydasının yalnızca 2 ve 5'in çarpanlarından oluşması gerekir (10 = 2 × 5). 1/4 = 0,25 sonludur; 1/3 sonsuzdur.

Bilgisayarlar ise ikili sistemde çalışır ve tabanları sadece 2'dir. Bu sistemde bir kesrin sonlu olabilmesi için paydasının yalnızca 2'nin kuvvetlerinden oluşması gerekir. 0,5 (1/2), 0,25 (1/4), 0,125 (1/8) sorunsuzdur. Ama 0,1 = 1/10 ve 10'un içinde 5 var. Sonuç:

0,1 (onluk) = 0,0001100110011001100110011... (ikili)

"0011" deseni sonsuza kadar tekrar eder. 0,1, bilgisayar için bizim 1/3'ümüz.

Kayan nokta nedir?

Bilgisayarlar ondalıklı sayıları çoğunlukla IEEE 754 adlı standarda göre tutar. En yaygın biçim olan "double" (çift duyarlıklı) sayı 64 bit kullanır:

Bölüm Bit sayısı Görevi
İşaret 1 Artı mı eksi mi
Üs 11 Virgülün nerede olduğu
Kesir (mantis) 52 Anlamlı basamaklar

Adı da buradan gelir: virgül sabit değildir, sayının büyüklüğüne göre "kayar". Bu sayede aynı 64 bit ile hem atomların boyutunu hem de galaksiler arası mesafeleri ifade edebilirsiniz. Karşılığında her sayı yaklaşık 15-17 anlamlı basamak hassasiyetle tutulur.

52 bitlik kesir alanı, 0,1'in sonsuz ikili açılımını bir yerde keser ve yuvarlar. Bilgisayarın hafızasındaki "0,1" aslında tam olarak şudur:

0.1000000000000000055511151231257827021181583404541015625

Bunu kendiniz de görebilirsiniz. Python'da:

from decimal import Decimal
print(Decimal(0.1))
# 0.1000000000000000055511151231257827021181583404541015625

0,2 de benzer şekilde biraz fazlasıyla saklanır. İkisini topladığınızda küçük hatalar birleşir ve sonuç, 0,3'ün saklanabilen en yakın hâlinden bir adım öteye düşer: 0.30000000000000004.

Neden ekranda 0.1 görüyoruz?

Madem 0,1 aslında 0,1000000000000000055..., neden console.log(0.1) bize tertemiz "0.1" gösteriyor? Çünkü programlama dilleri sayıyı yazdırırken, aynı değere geri dönüştürülebilen en kısa ondalık gösterimi seçer. 0,1'in saklanan hâline en yakın kısa sayı "0.1" olduğu için onu görürüz. Toplamanın sonucu ise 0,3'ün saklanan hâlinden farklı bir değere denk geldiği için dil onu 0.3 olarak gösteremez ve bütün basamakları döker.

Bu küçük hata insan hayatına mal oldu

Kayan nokta hataları her zaman zararsız değil. 1991'de Körfez Savaşı sırasında bir Patriot füze savunma bataryası, saniyenin onda birini (1/10) ikili sistemde tam gösteremediği için zamanı her ölçümde minik bir hatayla tuttu. Yaklaşık 100 saat kesintisiz çalışan sistemde hata 0,34 saniyeye ulaştı ve gelen bir Scud füzesi durdurulamadı; 28 asker hayatını kaybetti. Olayın ayrıntılarını tarihin en pahalı yazılım hataları yazımızda anlattık. Burada önemli olan şu: sorun, tam da bu yazıda anlattığımız 0,1'in ikili sistemdeki sonsuz açılımıydı.

Borsa endeksi nasıl yarıya düştü?

Daha az trajik ama çok öğretici bir başka örnek: 1982'de Vancouver Borsası, 1.000 değeriyle yeni bir endeks başlattı. Endeks her işlemden sonra yeniden hesaplanıyor ve sonuç virgülden sonra üç basamağa yuvarlanmak yerine kesiliyordu. Günde binlerce kez tekrarlanan bu minik kesintiler hep aynı yöne, aşağıya doğru birikti. Yaklaşık iki yıl sonra endeks 520 civarındaydı. Hesap düzeltildiğinde gerçek değerin 1.000'in üzerinde olduğu ortaya çıktı.

Ders açık: tek başına önemsiz görünen bir yuvarlama hatası, milyonlarca kez tekrarlandığında sonucun kendisini değiştirebilir.

Para hesaplarında ne yapmalı?

E-ticaret, muhasebe, bankacılık ya da faturalama yazılımı geliştiriyorsanız kural basit: parayı asla float ya da double ile tutmayın. Üç güvenli yöntem var:

1. En küçük birim cinsinden tam sayı kullanın. 12,50 TL'yi 1250 kuruş olarak tutun. Tam sayılarda toplama ve çıkarma kesindir. Sadece ekranda gösterirken 100'e bölersiniz.

2. Ondalık veri türü kullanın. Çoğu dil, onluk tabanda hesap yapan bir tür sunar:

// Java: BigDecimal'i her zaman metinden oluşturun
new BigDecimal("0.1").add(new BigDecimal("0.2")); // 0.3

// Yanlış: double'dan oluşturmak hatayı içeri taşır
new BigDecimal(0.1); // 0.1000000000000000055511151231257827...
from decimal import Decimal
Decimal("0.1") + Decimal("0.2")  # Decimal('0.3')

C# tarafında decimal türü aynı işi görür.

3. Veritabanında doğru sütun türü seçin. Tutarlar için FLOAT veya DOUBLE değil, NUMERIC(12,2) / DECIMAL kullanın.

Bir de yuvarlama kuralına dikkat edin: KDV gibi hesaplarda yuvarlamanın satır bazında mı yoksa toplam üzerinden mi yapılacağı ve hangi yuvarlama yönteminin kullanılacağı iş kuralıdır. Bunu kodun rastgele davranışına bırakmayın.

Ondalık sayıları nasıl karşılaştırmalı?

Bilimsel ya da grafik hesaplarında kayan nokta kullanmak doğru tercihtir; hız ve aralık açısından rakipsizdir. Ama iki kayan noktalı sayıyı asla == ile karşılaştırmayın. Bunun yerine farkın küçük bir tolerans içinde olup olmadığına bakın:

Math.abs((0.1 + 0.2) - 0.3) < Number.EPSILON; // true

Burada Number.EPSILON, 1 ile ondan büyük en küçük double arasındaki farktır. Büyük sayılarla çalışıyorsanız toleransı sayıların büyüklüğüne göre ölçeklemeniz gerekir.

Sıkça sorulan sorular

Bu bir JavaScript hatası mı?

Hayır. IEEE 754 standardını kullanan tüm diller aynı sonucu verir: Python, Java, C#, C, Go, Ruby ve daha fazlası. JavaScript'te sadece daha görünürdür çünkü tüm sayılar varsayılan olarak double'dır.

Excel'de de bu sorun var mı?

Evet, Excel de sayıları IEEE 754 double olarak tutar ve 15 anlamlı basamak gösterir. Bazı formüllerde beklenmedik küçük farklar görebilirsiniz; hassas hesaplarda YUVARLA fonksiyonunu bilinçli kullanmak gerekir.

Neden bilgisayarlar onluk sistemi kullanmıyor?

Kullanabilirler ve ondalık veri türleri tam olarak bunu yapar. Ama ikili sistem donanım açısından çok daha hızlı ve verimlidir. Bilimsel hesap, grafik ve yapay zekâ gibi alanlarda bu hız, minik yuvarlama farklarından çok daha değerlidir.

Oyunlarda ve yapay zekâda da kayan nokta mı kullanılıyor?

Evet. Hatta yapay zekâ modelleri, hız ve bellek kazanmak için çoğu zaman 16 ya da 8 bitlik, çok daha düşük hassasiyetli kayan nokta biçimleri kullanır. Bu alanlarda küçük hatalar sonucu etkilemez; paranın aksine.

0,1 + 0,2, yazılımda "doğru görünen" ile "doğru olan" arasındaki farkın en güzel örneklerinden biri. Finans, e-ticaret ya da faturalama gibi hassas hesaplar yapan sistemler geliştirmek isterseniz kurumsal yazılım geliştirme sayfamızdan bize ulaşabilirsiniz.

Kaynaklar

PaylaşLinkedInXWhatsApp
Bu konuda yardıma mı ihtiyacınız var?

Yazıda anlattıklarımızı kendi projenize uygulamak isterseniz birlikte bakalım.

Bize yazın
YE

EngerekTech’in kurucusu. İşletmeler için web, mobil ve kurumsal yazılım geliştiriyor; Angular, Spring Boot ve Flutter ile çalışıyor, KPSS Düello ve Kelime Kavanozu uygulamalarını geliştirdi. Blogda yapay zekâ araçlarını ve yazılım geliştirmeyi kendi projelerinde kullandığı haliyle anlatıyor.