İçeriğe geç

float — IEEE 754, '0.1 + 0.2' Mistik Hatası ve Precision'ın Karanlık Sanatı

0.1 + 0.2 == 0.3 yazınca Python False döner. Bu bug değil — IEEE 754 standardının 1985'ten beri yaşayan gerçeği. Bu derste float'un içsel yapısını (sign+exponent+mantissa), neden bazı sayıların binary'de 'sonsuz tekrar ettiğini', math.isclose'un nasıl kullanıldığını, NaN/inf davranışlarını, AI/ML'de neden float32 baskın olduğunu ve finansal hesapta float'tan kaçınmanın hayati önemini öğreniyoruz.

Şükrü Yusuf KAYA
25 dakikalık okuma
Başlangıç
float — IEEE 754, '0.1 + 0.2' Mistik Hatası ve Precision'ın Karanlık Sanatı
🚨 Bu ders her programcının baş ağrısı olan bir konuya cevap veriyor
Bir gün Python REPL'inde
0.1 + 0.2
yazdın, Enter bastın, ekranda
0.30000000000000004
gördün. 'Python bozuk mu?' diye düşündün. Hayır. Python tam doğru yapıyor. Hatalı düşünen sensin (bilmediğinden değil — okulun matematik dersinde sana 'bilgisayarlar her sayıyı tam tutar' dedi, bu yalandı). Bu derste 'neden bozuk değil' sorusunun ardındaki 30 yıllık IEEE 754 standardını inceliyoruz. Sonra finans/AI/bilim dünyalarında bu gerçekle nasıl yaşanılır onu konuşuyoruz.

Önce küçük bir gözlem#

Python REPL'inde şunu yaz:
>>> 0.1 + 0.2 0.30000000000000004 >>> 0.1 + 0.2 == 0.3 False
İlk gördüğünde sarsıcı. Sanki Python'da temel matematik bozuk. Aslında bozuk değil — sen yanlış soruyorsun.
Neden? Çünkü bilgisayar belleği sınırlı. Sınırlı bellekte sınırsız hassasiyet (precision) imkansız. Bilgisayar her sayıyı yaklaşık olarak tutar.
0.1
ondalık sayısı, binary'de sonsuz tekrar eden bir sayıya karşılık geliyor:
0.1 (decimal) = 0.0001100110011001100110011... (binary, sonsuz)
Tıpkı
1/3
'ün decimal'de 0.33333... şeklinde sonsuz tekrar etmesi gibi. Bilgisayar 64 bit'te kesip durduruyor. Yani saklanan değer aslında
0.1
değil —
0.1
'e çok yakın bir başka sayı.
0.2
da öyle. Hatasız topla, ama saklanan değerler "tam değil" — sonuç da tam
0.3
olmuyor; ona çok yakın bir şey.
Bu davranış Python'a özgü değil. C, Java, JavaScript, Excel, hepsi aynı. Çünkü hepsi IEEE 754 standardını kullanıyor. Bu standartla bir kez tanışınca, 30 yıllık bir hayat sırrını öğrenmiş olursun.

IEEE 754 — kısa tarih#

1985'ten önce, her bilgisayar üreticisi kendi float formatını kullanıyordu. IBM'in formatı vardı, DEC'in formatı vardı, Cray'in formatı vardı. Sonuç: aynı program farklı bilgisayarda farklı sonuç veriyordu — bilim için felaket.
1985'te IEEE 754 standardı yayınlandı. William Kahan (Berkeley'den) bu standartın baş mimarı; bu çalışması için 1989'da Turing Award (CS'in Nobel'i) aldı.
IEEE 754 birkaç float formatı tanımlıyor:
FormatBitRange (yaklaşık)Decimal precisionKullanım
half (FP16)16±6.5×10⁴~3-4 haneAI/ML inference (VRAM tasarruf)
bfloat1616±3.4×10³⁸~2-3 haneGoogle TPU, modern AI training
single (FP32)32±3.4×10³⁸~7 haneGrafik, AI training/inference
double (FP64)64±1.8×10³⁰⁸~15-17 haneBilim, finans (yetersiz olsa da), genel
quadruple (FP128)128±1.2×10⁴⁹³²~33-36 haneYüksek-precision bilim (nadir)
Python'un
float
tipi: 64-bit double precision (FP64)
.
Yani senin
x = 3.14
yazdığında bilgisayar 64 bit kullanıyor. Bu yeterince geniş ama sınırsız değil. Sonsuz tekrar eden sayıları kesiyor — precision kaybı buradan geliyor.

64-bit double'ın anatomisi#

64 bit şöyle bölünüyor:
[ Sign | Exponent | Mantissa (Fraction) ] [ 1 | 11 | 52 ]
Sign: 0 = pozitif, 1 = negatif. (1 bit)
Exponent: Üs değeri (binary). 11 bit, biased (1023 ekleniyor). Range: 2⁻¹⁰²² ile 2¹⁰²³ arası.
Mantissa: 1 ile 2 arasında bir kesir. Implicit "1." önek dahil. 52 bit precision, "1." ile birlikte 53 bit etkin precision.
Format:
(-1)^sign × 1.mantissa × 2^(exponent - 1023)

Örnek:
3.14
'ün binary gösterimi#

3.14 (decimal) = 11.001000111101011100001010001111010111000010100011111... (binary, sonsuz tekrar) 64-bit double'da: - Sign: 0 (pozitif) - Exponent: 1024 (1023 + 1, çünkü ham exp = 1) - Mantissa: 1.5707963267948965... (sonsuz) Yaklaşık olarak saklanan: 3.14000000000000012434497875801753...
Yani
x = 3.14
aslında bellekte tam olarak 3.14 değil. Çok yakın bir başka sayı. Aradaki fark "machine epsilon" (
sys.float_info.epsilon
= ~2.22e-16).
Genelde bu fark görünmez —
print(x)
yapınca Python akıllıca yuvarlıyor, "3.14" gösteriyor. Ama hassas hesapta (özellikle subtraction, repeated operations) fark birikip büyüyor.
>>> import sys >>> sys.float_info sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1)
Bu output, Python'ın float yapısını anlatan API. Önemli alanlar:
  • max
    : Saklanabilen en büyük sayı (~1.8×10³⁰⁸). Üzerinde
    inf
    .
  • min
    : En küçük pozitif normal sayı (~2.2×10⁻³⁰⁸). Altında subnormal.
  • epsilon
    : 1.0 + epsilon ≠ 1.0 olan en küçük epsilon (~2.2×10⁻¹⁶).
  • dig
    : Güvenle saklanan ondalık basamak (~15).
  • mant_dig
    : Mantissa precision (53 bit, "1." dahil).
python
# Bir float'un binary temsilini görmek için
import struct
 
def float_to_bin(f):
"""Float'u 64-bit binary string olarak göster."""
[d] = struct.unpack(">Q", struct.pack(">d", f))
return f"{d:064b}"
 
def bin_breakdown(f):
"""Float'u sign + exponent + mantissa olarak parçala."""
bits = float_to_bin(f)
sign = bits[0]
exp = bits[1:12]
mantissa = bits[12:]
return f"sign={sign} exp={exp} mantissa={mantissa}"
 
# Test
print(f"3.14 = {bin_breakdown(3.14)}")
# 3.14 = sign=0 exp=10000000000 mantissa=1001000111101011100001010001111010111000010100011111
 
print(f"0.1 = {bin_breakdown(0.1)}")
# 0.1 = sign=0 exp=01111111011 mantissa=1001100110011001100110011001100110011001100110011010
# ^^^^^^^^^^^^^^^^ -- "1001" pattern sonsuz tekrar
 
print(f"0.5 = {bin_breakdown(0.5)}")
# 0.5 = sign=0 exp=01111111110 mantissa=0000000000000000000000000000000000000000000000000000
# -- TAM SAKLANIYOR (binary'de exact)
 
print(f"1.0 = {bin_breakdown(1.0)}")
# 1.0 = sign=0 exp=01111111111 mantissa=0000000000000000000000000000000000000000000000000000
 
# .as_integer_ratio() — hangi rasyonel sayıya tam olarak eşit?
print(f"\n0.1 saklanan kesir: {(0.1).as_integer_ratio()}")
# (3602879701896397, 36028797018963968)
# Yani 0.1 olarak yazsan da, 0.1 değil — çok yakın bir kesir.
 
# Bu kesirin gerçek değeri:
n, d = (0.1).as_integer_ratio()
print(f"Gerçek değer: {n / d}")
print(f"Pre-stored: {repr(0.1)}") # 0.1
print(f"Tam: {0.1:.55f}")
# 0.1000000000000000055511151231257827021181583404541015625
# Bak — "0.1" değil, ama görüntüde "0.1" diye yazılıyor
Float'un içsel temsilini görmek. 0.1 saklanırken 'tam' değildir — bu ders boyunca tekrar tekrar göreceğin gerçek.

"0.1 + 0.2" tuzağı — adım adım#

Bu klasiği detayda çözelim:
>>> a = 0.1 >>> b = 0.2 >>> c = a + b >>> c 0.30000000000000004 >>> c == 0.3 False
Ne oluyor?
  1. 0.1
    saklamak için: aslında
    0.1000000000000000055...
    saklanıyor (binary'de sonsuz tekrar nedeniyle).
  2. 0.2
    saklamak için: aslında
    0.2000000000000000111...
    saklanıyor.
  3. İkisini topluyorsun:
    0.30000000000000004...
    (yaklaşık).
  4. 0.3
    'ü compare etmek için:
    0.2999999999999999889...
    saklanmıştı.
Yani iki yaklaşık sayı + üçüncü yaklaşık sayı = küçük fark.
Bu sadece toplama değil. Çıkarma, çarpma, bölme — her float işleminde küçük rounding hataları olur. Bu hatalar birikir (accumulation error). 1000 işlem yapan algoritmada hata ciddi seviyeye ulaşabilir.
Pratik kural: Float'ları asla
==
ile karşılaştırma
. Onun yerine
math.isclose()
kullan.
import math >>> math.isclose(0.1 + 0.2, 0.3) True >>> math.isclose(1.0, 1.0000001) False >>> math.isclose(1.0, 1.0000001, rel_tol=1e-5) True
math.isclose
iki tolerans kullanıyor:
  • rel_tol: Göreceli tolerans (a ve b'nin büyüklüğüne göre). Default: 1e-9.
  • abs_tol: Mutlak tolerans. Default: 0.0.
Formül:
|a - b| <= max(rel_tol * max(|a|, |b|), abs_tol)
Sıfıra yakın değerlerde
abs_tol
kullan. Büyük değerlerde
rel_tol
yeter.
python
# math.isclose ile pratik
import math
 
# Klasik float trap
print(0.1 + 0.2 == 0.3) # False
print(math.isclose(0.1 + 0.2, 0.3)) # True
 
# Çok küçük sayılar (sıfıra yakın)
print(math.isclose(1e-10, 0)) # False (default rel_tol)
print(math.isclose(1e-10, 0, abs_tol=1e-9)) # True
 
# Çok büyük sayılar
print(math.isclose(1e20, 1e20 + 1)) # True (rel_tol yeter)
print(math.isclose(1e20, 1e20 + 1e10)) # False (fark fazla)
print(math.isclose(1e20, 1e20 + 1e10, rel_tol=1e-5)) # True (toleransı gevşet)
 
# Numpy benzeri allclose (çoklu eleman)
import numpy as np
a = np.array([0.1 + 0.2, 1.0, 2.0])
b = np.array([0.3, 1.0, 2.000001])
print(np.allclose(a, b)) # True
 
# Helper fonksiyon — projendeki tüm float karşılaştırmaları
def float_equal(a, b, tol=1e-9):
"""Float karşılaştırma için yardımcı."""
if math.isnan(a) or math.isnan(b):
return False # NaN'ler asla eşit değil
return math.isclose(a, b, rel_tol=tol, abs_tol=tol)
 
# Test
assert float_equal(0.1 + 0.2, 0.3)
assert not float_equal(1.0, 2.0)
assert not float_equal(float('nan'), float('nan')) # NaN trap
 
print("Tüm assertion'lar geçti.")
Float karşılaştırma kütüphanesi. Bu helper'ı projenize alıp kullanabilirsin.

NaN — "Not a Number" — IEEE 754'ün antimadde'si#

Bazı işlemler matematiksel olarak tanımsız. Sıfıra bölme, negatif sayının kare kökü, sonsuzdan sonsuzu çıkarma...
IEEE 754 bunlar için özel bir değer ayırdı: NaN (Not a Number).
>>> import math >>> math.nan nan >>> float('nan') nan >>> 0.0 / 0.0 # ZeroDivisionError (Python int/float bölme) # Ama: >>> import numpy as np >>> np.float64(0) / np.float64(0) # Numpy: nan nan >>> np.sqrt(-1) nan
NaN'in en garip özelliği: NaN ≠ NaN.
>>> nan = float('nan') >>> nan == nan # False! False >>> nan != nan # True! True >>> nan == 1 False >>> nan < 1 False >>> nan > 1 False >>> nan <= 1 False >>> nan >= 1 False
Bu davranış kasıtlı. NaN, "anlamsız değer" demek; iki anlamsız değer aynı veya farklı olamaz. Karşılaştırma sonucu hep
False
.
NaN'i tespit etmek için
math.isnan
veya
math.isfinite
:
>>> math.isnan(nan) # True >>> math.isnan(1.0) # False >>> math.isfinite(nan) # False >>> math.isfinite(1.0) # True >>> math.isfinite(float('inf')) # False

NaN propagation#

NaN'le yapılan her işlem NaN dönüyor:
>>> nan + 5 nan >>> nan * 0 nan >>> nan ** 0 # En garibi 1.0 >>> max(nan, 5) # min/max davranışı belirsiz, implementation-defined nan
Bu yüzden veri pipeline'larında bir hücrede NaN olunca, hesaplar sürekli NaN üretiyor. Pandas'ta
df.dropna()
,
df.fillna()
ile temizleme zorunlu.

Infinity —
inf
ve
-inf
#

IEEE 754'ün ikinci özel değeri: sonsuz.
>>> math.inf inf >>> -math.inf -inf >>> float('inf') inf # Aritmetik >>> 1.0 / 0.0 # ZeroDivisionError (Python) >>> math.inf + 1 # inf >>> math.inf - math.inf # nan! (tanımsız) >>> math.inf * 0 # nan! (tanımsız) >>> -math.inf + math.inf # nan >>> math.inf > 1e308 # True (inf en büyük) # Karşılaştırma >>> math.inf == math.inf # True (NaN'ın aksine, inf eşittir) >>> math.inf > 999999999999999 # True # Kullanım: başlangıç değerleri def find_min(numbers): minimum = math.inf # her şey bundan küçük for n in numbers: if n < minimum: minimum = n return minimum print(find_min([3, 1, 4, 1, 5, 9, 2, 6])) # 1
-inf
ve
inf
'i max/min başlangıç değeri olarak kullanmak yaygın bir pattern. Sentinel value yerine matematiksel olarak doğru.

Inf overflow#

Çok büyük hesaplar inf üretebilir:
>>> 2.0 ** 1024 OverflowError: (34, 'Result too large') >>> 2.0 ** 1023.99 1.7958571726983093e+308 >>> 2.0 ** 1024 OverflowError # Ama: >>> import math >>> math.exp(710) OverflowError >>> math.exp(709) 8.218407461554972e+307 # Sıfır altı (underflow): >>> 2.0 ** -1075 0.0 # underflow → 0
Bunlar IEEE 754 saklama sınırları.
sys.float_info.max
(~1.8e308) üzerinde
inf
;
sys.float_info.min
(~2.2e-308) altında subnormal başlıyor, sonra 0.

Precision sınırı — neden sadece ~15 hane#

sys.float_info.dig
= 15. Yani double precision güvenli ondalık basamak sayısı: 15.
Bunun anlamı:
>>> x = 1.234567890123456789 >>> print(f"{x:.20f}") 1.23456789012345677788
İlk 15 hane (
1.23456789012345
) doğru. 16. ve sonrası — uyduruyor (random bit'ler). Yani:
>>> a = 1.234567890123456 >>> b = 1.234567890123457 >>> a == b False # Aralarındaki fark gösterilebiliyor >>> a + 0.000000000000001 1.234567890123457 # Doğru toplandı
Ama 16+ hane:
>>> a = 1.2345678901234567890 >>> b = 1.2345678901234567891 >>> a == b True # 17. ve sonraki haneleri kaybetti
Pratik etki: Çok yüksek precision gerekirse
Decimal
(sonraki ders) veya
Fraction
modülünü kullan.

Büyük integer'larla precision kaybı#

>>> 10 ** 17 + 1 100000000000000001 # int olarak doğru >>> float(10 ** 17 + 1) 1e+17 # float'a çevirince precision kaybı >>> float(10 ** 17 + 1) == float(10 ** 17) True # 1 fark "yutuldu" — float saklayamadı >>> int(float(10 ** 17 + 1)) - 10 ** 17 0 # 1 kaybı
Yani 2^53 = ~9×10^15 üzerinde integer'ı float olarak güvenli saklayamazsın. JavaScript bütün integer'ları number tip olarak tuttuğu için bu sorun JS dünyasında "BigInt" kavramını doğurdu. Python int sınırsız (önceki ders) — bu sorun yok.

Float vs int conversion#

>>> int(3.14) # 3 (truncate, sıfıra doğru) >>> int(-3.14) # -3 (truncate, sıfıra doğru) >>> int(3.99) # 3 (yuvarlama yok) >>> int(-3.99) # -3 >>> round(3.5) # 4 (NOT 4 — bankers rounding!) >>> round(2.5) # 2 (en yakın çift sayıya yuvarlar — IEEE 754 default) >>> round(3.14159, 2) # 3.14 >>> round(3.14159, 0) # 3.0 (float dönüyor!) >>> import math >>> math.floor(3.7) # 3 >>> math.ceil(3.2) # 4 >>> math.trunc(-3.7) # -3 (sıfıra doğru, int() ile aynı)
round
'un "bankers rounding" davranışı sürpriz yapar —
round(0.5) == 0
,
round(1.5) == 2
. Sebebi: istatistiksel bias'ı azaltma. Klasik "yarıyı yukarı" yuvarlama istiyorsan Decimal kullan.

Float'u güzel yazdırma — format spec#

Float'lar default olarak fazla detayla yazdırılıyor. Format spec ile kontrol et:
x = 3.14159265358979 n = 1234567.89 # Decimal places f"{x:.2f}" # '3.14' f"{x:.5f}" # '3.14159' f"{x:.0f}" # '3' (yuvarlama!) f"{x:.10f}" # '3.1415926536' # Scientific f"{x:e}" # '3.141593e+00' f"{x:.3e}" # '3.142e+00' f"{n:.2e}" # '1.23e+06' # Auto (g — anlamlı basamak sayısı) f"{x:g}" # '3.14159' f"{x:.3g}" # '3.14' f"{1000000.0:g}" # '1e+06' # Width + alignment + decimal f"{x:10.2f}" # ' 3.14' (10 karakter, 2 ondalık) f"{x:<10.2f}" # '3.14 ' (sola hizalı) f"{x:>10.2f}" # ' 3.14' (sağa) f"{x:^10.2f}" # ' 3.14 ' (ortalı) # Thousand separator f"{n:,.2f}" # '1,234,567.89' f"{n:,.0f}" # '1,234,567' # Türkçe locale (ondalık virgül, binlik nokta) için locale modülü import locale locale.setlocale(locale.LC_ALL, 'tr_TR.UTF-8') locale.format_string("%.2f", n, grouping=True) # '1.234.567,89' # Yüzdelik f"{0.1234:.2%}" # '12.34%' f"{0.1234:.0%}" # '12%' # Sign control f"{x:+.2f}" # '+3.14' f"{-x:+.2f}" # '-3.14' f"{x: .2f}" # ' 3.14' (boşluk pozitif sayıda) # Filled (0-padded) f"{42.5:08.2f}" # '00042.50'
Bu format spec'i (
{val:fmt}
) f-string'in temeli. Ders 3 (string'ler — Modül 3) detayda işliyor; ama float için bilmesi gereken pattern'ler bunlar.

math
modülü — float ile çalışırken yardımcılar#

Python'un standart kütüphanesinde
math
modülü, float üzerinde matematik işlemleri sunuyor:
import math # Sabitler math.pi # 3.141592653589793 math.e # 2.718281828459045 math.tau # 6.283185307179586 (2π) math.inf # inf math.nan # nan # Trigonometri (radyan ile) math.sin(math.pi / 2) # 1.0 math.cos(0) # 1.0 math.tan(math.pi / 4) # 0.9999999999999999 (precision!) # Derece ↔ Radyan math.radians(180) # 3.141592653589793 math.degrees(math.pi) # 180.0 # Üs / log math.exp(1) # 2.718281828459045 (= e^1) math.log(math.e) # 1.0 (natural log, ln) math.log(100, 10) # 2.0 (log base 10) math.log10(100) # 2.0 math.log2(1024) # 10.0 # Kök math.sqrt(16) # 4.0 math.cbrt(27) # 3.0 (3.11+) math.pow(2, 10) # 1024.0 (1024.0 — float, ** ile farkı: math.pow always float) # Yuvarlama math.floor(3.7) # 3 math.ceil(3.2) # 4 math.trunc(-3.7) # -3 (sıfıra doğru) # Mutlak değer (abs() built-in da yapar) math.fabs(-3.14) # 3.14 (her zaman float) abs(-3) # 3 (int için int dönüyor) # Modulus math.fmod(10, 3) # 1.0 (C tarzı, sıfıra doğru — Python % operatöründen farklı!) 10 % 3 # 1 (Python — divisor işareti) # GCD / LCM (int için) math.gcd(12, 18) # 6 math.lcm(4, 6) # 12 (3.9+) # Toplam (precision-aware) math.fsum([0.1] * 10) # 1.0 (kümulatif hata düzeltmeli) sum([0.1] * 10) # 0.9999999999999999 (klasik toplam) # Yakınlaşma kontrolleri math.isclose(a, b) # tolerance ile karşılaştırma math.isnan(x) # NaN check math.isfinite(x) # ne nan ne inf math.isinf(x) # inf veya -inf # Faktöriyel, kombinasyon math.factorial(10) # 3628800 math.comb(10, 3) # 120 (10 choose 3) math.perm(10, 3) # 720 # Hipotenüs (Pythagorean) math.hypot(3, 4) # 5.0 math.hypot(1, 2, 2) # 3.0 (multi-dim, 3.8+) # Distance (3.8+) math.dist((0, 0), (3, 4)) # 5.0 # Floor div / mod birlikte (büyük int için) math.floor(7 / 2) # 3 divmod(7, 2) # (3, 1) — int için
math
modülünün metodları C ile yazıldığı için hızlı. NumPy yoksa float matematik için bunlar.
🎯 Önemli pattern: Toplama yaparken
math.fsum
kullanmak — kümülatif hata azaltır:
data = [0.1] * 1000 # Klasik toplam sum(data) # 99.99999999999986 (hata) # fsum ile (Kahan-Babuška-Neumaier algoritması) math.fsum(data) # 100.0 (tam!)
Veri analizinde, simülasyonda — büyük listeleri toplarken
math.fsum
veya NumPy'nin akümülatif fonksiyonlarını tercih et.

NumPy float'ları — AI/ML dünyasının dili#

NumPy 5 farklı float tipi sunar:
import numpy as np # 16-bit half (FP16) — eski; AI inference'da yaygındı np.float16 # ±6.5e4, 3-4 decimal precision np.array([1.5], dtype=np.float16) # 16-bit bfloat (BF16) — Google TPU'nun standardı # Range FP32 gibi, precision daha düşük # Note: Python'da native bfloat16 yok; PyTorch/TensorFlow ile # 32-bit single (FP32) — AI training/inference standardı np.float32 # ±3.4e38, ~7 decimal arr = np.array([1.0, 2.0], dtype=np.float32) # 64-bit double (FP64) — Python'un default'u np.float64 # ±1.8e308, ~15-17 decimal np.array([1.0]) # default = float64 # 128-bit (sistem desteğine bağlı) np.float128 # ~33-36 decimal precision

Neden AI/ML için float32?#

# 1 milyon parametreli sinir ağı fp32_size = 1_000_000 * 4 # 4 byte/param = 4 MB fp64_size = 1_000_000 * 8 # 8 byte/param = 8 MB fp16_size = 1_000_000 * 2 # 2 byte/param = 2 MB # GPT-3: 175 milyar parametre # fp32: 700 GB # fp16: 350 GB # Çok büyük modelleri RAM'e sığdırmak için float boyutu kritik
float32 AI'ın altın standardı:
  • Yeterli precision (genelde)
  • 2x az bellek (fp64'e göre)
  • 2-4x daha hızlı (GPU tensor core'ları fp16/fp32'de optimal)
float16 ve bfloat16 inference'da yaygın:
  • Daha az bellek
  • Daha hızlı
  • Precision azalmış ama "yeterli" çoğu işte
PyTorch/TensorFlow kodunda göreceksin:
import torch # Default float32 x = torch.randn(10) # float32 print(x.dtype) # torch.float32 # float16'a indir x_half = x.half() # float16 # bfloat16 x_bf = x.bfloat16() # Mixed precision training (modern) with torch.cuda.amp.autocast(): y = model(x) # otomatik fp16/fp32 mix
Bu detayı şimdilik yüzeysel bil; Modül 25 (Deep Learning) detaylı işliyor. Burada sadece "neden float'un boyut'unu seçiyoruz" anlatıyoruz.
python
# Finansal hesapta float NEDEN KÖTÜ — somut örnek
 
# Senaryo: Online mağaza sepeti
prices = [9.99, 14.99, 4.99, 19.99]
 
# Naif toplam
total = sum(prices)
print(f"Total: {total}") # 49.96
 
# %18 KDV ekle
vat_rate = 0.18
total_with_vat = total * (1 + vat_rate)
print(f"With VAT: {total_with_vat}") # 58.9528 — aslında 58.9528 değil!
 
# Daha kötüsü: kümülatif hata
def add_with_vat(items):
total = 0
for item in items:
total += item * 1.18
return total
 
cumulative = add_with_vat(prices)
print(f"Cumulative: {cumulative}") # 58.9528 belki, belki 58.95280000000001
 
# Karşılaştırma
print(total_with_vat == cumulative) # FALSE bazen!
 
# Düşük tutarlarda görünmez ama büyük hacimde:
import random
big_sale = [round(random.uniform(0.01, 999.99), 2) for _ in range(1_000_000)]
 
# Yöntem 1
total_1 = sum(price * 1.18 for price in big_sale)
 
# Yöntem 2
subtotal = sum(big_sale)
total_2 = subtotal * 1.18
 
# Aynı matematik, farklı sonuç
print(f"Method 1: {total_1}")
print(f"Method 2: {total_2}")
print(f"Diff: {abs(total_1 - total_2)}")
# Diff: birkaç kuruş! Yıllık binlerce işlem birikince binlerce TL fark.
 
# Çözüm: Decimal — sonraki ders
from decimal import Decimal, getcontext
getcontext().prec = 28
 
prices_dec = [Decimal('9.99'), Decimal('14.99'), Decimal('4.99'), Decimal('19.99')]
total_dec = sum(prices_dec)
total_with_vat_dec = total_dec * Decimal('1.18')
print(f"\nDecimal total with VAT: {total_with_vat_dec}")
# 58.9528 (TAM. Hatasız.)
Finansal hesapta float kullanma. Bu kadar net. Decimal modülü için Ders 6'yı bekle.

Yaygın float tuzakları#

1. Eşitlik karşılaştırması#

# 🚫 Asla if x == 0.3: ... # ✅ Her zaman if math.isclose(x, 0.3): ...

2. Toplam birikim hatası#

# 🚫 total = 0.0 for _ in range(10000): total += 0.1 print(total) # 999.9999999999062 # ✅ total = math.fsum([0.1] * 10000) print(total) # 1000.0

3. NaN dictionary key olarak#

nan = float('nan') d = {nan: "value"} print(d.get(nan)) # None! NaN != NaN, kontrolde başarısız print(d[nan]) # KeyError ya da değer? Implementation-defined
NaN'i key olarak kullanma. Sentinel için
None
kullan.

4. Range / sequence olarak float#

# 🚫 i = 0.0 while i < 1.0: print(i) i += 0.1 # Aşırı sürebilir veya 1.0'ı geçebilir (precision) # ✅ import numpy as np for i in np.arange(0, 1, 0.1): print(i) # Veya: for i in range(10): x = i / 10 print(x)

5. JSON serialization#

import json data = {"x": 0.1 + 0.2} json.dumps(data) # '{"x": 0.30000000000000004}' # JSON int yapamıyorsan float çıkıyor json.dumps({"y": 1.0}) # '{"y": 1.0}' json.dumps({"y": 1}) # '{"y": 1}'
JSON tüm sayıları tek tip olarak gösteriyor; float precision sızabilir. API yanıtlarında para tutarları için string kullanmak yaygın:
{"amount": "19.99"}
.

6. Subnormal (denormal) numbers#

>>> sys.float_info.min # 2.2250738585072014e-308 >>> 1e-308 # 1e-308 (normal) >>> 1e-310 # 1e-310 (subnormal — precision azalır) >>> 1e-324 # 5e-324 (en küçük subnormal) >>> 1e-325 # 0.0 (underflow)
Subnormal'lar precision düşük; performans da düşer (CPU tarafından özel kod yolu kullanılıyor). Bilim hesaplarında "neden bu adım yavaşladı?" sorusunun cevabı bazen subnormal'a girmektir.

Float ne zaman yeter, ne zaman Decimal lazım?#

DurumÖnerilen tip
Bilim, mühendislikfloat (FP64 yeter)
Grafik, oyunfloat (FP32 yeter, hız önemli)
AI/ML trainingfloat32 / bfloat16
AI/ML inferencefloat16 / int8 (quantized)
Genel hesaplamafloat (Python default FP64)
İstatistikfloat (binlerce eleman birikse bile fsum ile)
Para — ödeme, hesap, faturaDecimal
Para — döviz kuru, FX rateDecimal
Para — vergi, KDVDecimal
Para — istatistik (örn ortalama satış)float yeter (precision dert değil)
Tarih/saatdatetime, NOT float
Birim çevirme (cm → m)float yeter
Tam rasyonel sayıFraction (math)
Pratik kural: Para görünüşlü hesap → Decimal. Para görünmeyen → float. Performansta Decimal float'tan ~50-100x yavaş; ama doğruluk fiyatı ödemeye değer.
Sıradaki ders:
Decimal
ve
Fraction
modülleri. Bu derste anlattığım sorunların çözümleri.

Hands-on 1: Float karşılaştırma helper'ı#

Aşağıdaki fonksiyonu kendin yaz, test et:
import math def safe_float_eq(a, b, tolerance=1e-9): """ İki float'ı güvenli şekilde karşılaştır. NaN, inf, sıfır kenar durumlarına dikkat et. >>> safe_float_eq(0.1 + 0.2, 0.3) True >>> safe_float_eq(1.0, 1.0) True >>> safe_float_eq(float('nan'), float('nan')) False >>> safe_float_eq(float('inf'), float('inf')) True >>> safe_float_eq(0, 1e-10) True >>> safe_float_eq(0, 1) False """ # SENİN KODUN BURAYA pass # Test: # import doctest # doctest.testmod(verbose=True)
Çözüm:
def safe_float_eq(a, b, tolerance=1e-9): if math.isnan(a) or math.isnan(b): return False if math.isinf(a) or math.isinf(b): return a == b # inf == inf veya -inf == -inf çalışıyor return math.isclose(a, b, rel_tol=tolerance, abs_tol=tolerance)
Doctest çalıştırarak doğrula. Eğer geçmiyorsa, eksik durumu bul.

Hands-on 2: Kahan summation#

Klasik toplam algoritması kümülatif hata yapar. Kahan summation algoritması bu hatayı düzeltir. Aşağıdakini implemente et:
def kahan_sum(numbers): """ Kahan summation algorithm. Kümülatif rounding hatasını düzeltir. >>> kahan_sum([0.1] * 10) 1.0 >>> abs(kahan_sum([0.1] * 10000) - 1000.0) < 1e-9 True """ total = 0.0 c = 0.0 # compensation (kayıp precision) for x in numbers: y = x - c # düzeltilmiş input t = total + y # yüksek-precision toplam yapma denenir c = (t - total) - y # kaybedilen düşük-precision kısım total = t return total # Test print(kahan_sum([0.1] * 10)) # 1.0 print(sum([0.1] * 10)) # 0.9999999999999999 print(kahan_sum([0.1] * 10_000)) # 1000.0 print(sum([0.1] * 10_000)) # 1000.0000000001588 # math.fsum aslında benzer (ama daha gelişmiş — Neumaier variant) import math print(math.fsum([0.1] * 10_000)) # 1000.0
Bu pattern bilim/finans simülasyonlarında critical. Pandas
.sum()
aslında Kahan-benzeri kullanıyor.

Hands-on 3: Logaritmik faktöriyel (büyük sayılarda taşmadan)#

math.factorial(1000)
2568 basamaklı sayı verir. Çoğu hesaplamada (özellikle istatistikte) sadece logaritması lazım.
math.lgamma
bunu sağlıyor:
import math # Klasik factorial — büyük sayılarda taşar math.factorial(1000) # 2568 basamak — bellek/CPU yer # log(n!) — float'a sığar, hızlı math.lgamma(1001) # 5912.128178488163 (= log(1000!)) # Karşılaştır import math print(math.log(math.factorial(100))) # ~363.7393... (bu kadar çalıştırırsın) print(math.lgamma(101)) # ~363.7394 (anında) # Pratik: binom katsayısı log olarak def log_binomial(n, k): """log(C(n, k)) — büyük n için Decimal/Fraction'dan hızlı.""" return math.lgamma(n + 1) - math.lgamma(k + 1) - math.lgamma(n - k + 1) # 100'den 50 seçim sayısı print(log_binomial(100, 50)) # ~67.7 print(math.exp(log_binomial(100, 50))) # ~1.0089e29 print(math.comb(100, 50)) # 100891344545564193334812497256 # Yaklaşık eşit (precision sınırında)
İstatistik dersleri (Naive Bayes, ML modelleri) bu pattern'le doludur — log probabilities. Float çok büyük sayılarda taşar, log space'te güvenle çalışır.

Bu derste neler kazandın?#

IEEE 754 standardının doğum hikâyesi (1985, William Kahan, Turing Award).
64-bit double anatomi — sign + exponent + mantissa.
0.1 + 0.2 = 0.30000000000000004 sırrı çözüldü — binary'de sonsuz tekrar.
math.isclose
ile güvenli karşılaştırma;
rel_tol
vs
abs_tol
farkı.
NaN — eşit değil, propagation,
math.isnan
.
Infinity — sentinel, overflow,
math.inf
.
Precision sınırı — ~15 decimal hane, 2^53 üzeri integer'da kayıp.
Format spec
{:.2f}
,
{:,.0f}
,
{:.2%}
, scientific notation.
math
modülü
— sabitler, trig, exp/log, fsum (Kahan-Babuška).
NumPy float'ları (FP16/BF16/FP32/FP64) — AI/ML'de neden float32.
Finansal hesapta float'tan kaçın — KDV/sepet örneği, kümülatif hata.
6 yaygın tuzak — eşitlik, toplam birikim, NaN dict key, float range, JSON, subnormal.
Karar matrisi — ne zaman float, ne zaman Decimal/Fraction.
3 hands-on: safe_float_eq, kahan_sum, log_binomial.
✓ 6 derinlemesine konu accordion'da — hex floats, .hex(), FMA, float_info, platform tutarlılık, Goldberg makalesi.
Sıradaki ders:
complex
sayılar. Java/C++'da ekstra kütüphane gerekirken Python'da built-in.
3 + 4j
syntax, kutupsal koordinatlar, 2D rotation matrisi yerine complex çarpma, sinyal işleme'de neden temel — Hilbert/Fourier dersine uzanan köprü.

Sık Sorulan Sorular

Şu ana kadar **şanslıydın** ya da **görmedin**. Float hatası küçükken görünmüyor; büyük veride veya finansal raporlarda 'neden bu rapor 5 kuruş eksik' diye karşına çıkıyor. Bu dersi öğrenmek 1 saat, gelecekte 10 saatlik debug'tan kaçınma. Özellikle: parayla iş yapan herhangi bir kod yazmadan önce **bu konuyu mutlaka anla**. Asla `==` ile float karşılaştırma alışkanlığını şimdiden kazan.

Yorumlar & Soru-Cevap

(0)
Yorum yazmak için giriş yap.
Yorumlar yükleniyor...

İlgili İçerikler