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ıç🚨 Bu ders her programcının baş ağrısı olan bir konuya cevap veriyor
Bir gün Python REPL'inde yazdın, Enter bastın, ekranda 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.
0.1 + 0.20.30000000000000004Ö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.10.1 (decimal) = 0.0001100110011001100110011... (binary, sonsuz)
Tıpkı 'ün decimal'de 0.33333... şeklinde sonsuz tekrar etmesi gibi. Bilgisayar 64 bit'te kesip durduruyor. Yani saklanan değer aslında değil — 'e çok yakın bir başka sayı.
1/30.10.10.20.3Bu 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:
| Format | Bit | Range (yaklaşık) | Decimal precision | Kullanım |
|---|---|---|---|---|
| half (FP16) | 16 | ±6.5×10⁴ | ~3-4 hane | AI/ML inference (VRAM tasarruf) |
| bfloat16 | 16 | ±3.4×10³⁸ | ~2-3 hane | Google TPU, modern AI training |
| single (FP32) | 32 | ±3.4×10³⁸ | ~7 hane | Grafik, AI training/inference |
| double (FP64) | 64 | ±1.8×10³⁰⁸ | ~15-17 hane | Bilim, finans (yetersiz olsa da), genel |
| quadruple (FP128) | 128 | ±1.2×10⁴⁹³² | ~33-36 hane | Yüksek-precision bilim (nadir) |
Python'un tipi: 64-bit double precision (FP64).
floatYani senin 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.
x = 3.1464-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.143.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 aslında bellekte tam olarak 3.14 değil. Çok yakın bir başka sayı. Aradaki fark "machine epsilon" ( = ~2.22e-16).
x = 3.14sys.float_info.epsilonGenelde bu fark görünmez — yapınca Python akıllıca yuvarlıyor, "3.14" gösteriyor. Ama hassas hesapta (özellikle subtraction, repeated operations) fark birikip büyüyor.
print(x)>>> 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:
- : Saklanabilen en büyük sayı (~1.8×10³⁰⁸). Üzerinde
max.inf - : En küçük pozitif normal sayı (~2.2×10⁻³⁰⁸). Altında subnormal.
min - : 1.0 + epsilon ≠ 1.0 olan en küçük epsilon (~2.2×10⁻¹⁶).
epsilon - : Güvenle saklanan ondalık basamak (~15).
dig - : Mantissa precision (53 bit, "1." dahil).
mant_dig
python
# Bir float'un binary temsilini görmek içinimport 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}" # Testprint(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.1print(f"Tam: {0.1:.55f}")# 0.1000000000000000055511151231257827021181583404541015625# Bak — "0.1" değil, ama görüntüde "0.1" diye yazılıyorFloat'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?
- saklamak için: aslında
0.1saklanıyor (binary'de sonsuz tekrar nedeniyle).0.1000000000000000055... - saklamak için: aslında
0.2saklanıyor.0.2000000000000000111... - İkisini topluyorsun: (yaklaşık).
0.30000000000000004... - 'ü compare etmek için:
0.3saklanmıştı.0.2999999999999999889...
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 kullan.
==math.isclose()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- 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 kullan. Büyük değerlerde yeter.
abs_tolrel_tolpython
# math.isclose ile pratikimport math # Klasik float trapprint(0.1 + 0.2 == 0.3) # Falseprint(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ılarprint(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 npa = 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) # Testassert 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 .
FalseNaN'i tespit etmek için veya :
math.isnanmath.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 , ile temizleme zorunlu.
df.dropna()df.fillna()Infinity — inf ve -inf#
inf-infIEEE 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
-infinfInf 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ı. (~1.8e308) üzerinde ; (~2.2e-308) altında subnormal başlıyor, sonra 0.
sys.float_info.maxinfsys.float_info.minPrecision sınırı — neden sadece ~15 hane#
sys.float_info.digBunun anlamı:
>>> x = 1.234567890123456789 >>> print(f"{x:.20f}") 1.23456789012345677788
İlk 15 hane () doğru. 16. ve sonrası — uyduruyor (random bit'ler). Yani:
1.23456789012345>>> 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 (sonraki ders) veya modülünü kullan.
DecimalFractionBü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ı)
roundround(0.5) == 0round(1.5) == 2Float'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 () f-string'in temeli. Ders 3 (string'ler — Modül 3) detayda işliyor; ama float için bilmesi gereken pattern'ler bunlar.
{val:fmt}math modülü — float ile çalışırken yardımcılar#
mathPython'un standart kütüphanesinde modülü, float üzerinde matematik işlemleri sunuyor:
mathimport 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🎯 Önemli pattern: Toplama yaparken kullanmak — kümülatif hata azaltır:
math.fsumdata = [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 veya NumPy'nin akümülatif fonksiyonlarını tercih et.
math.fsumNumPy 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 sepetiprices = [9.99, 14.99, 4.99, 19.99] # Naif toplamtotal = sum(prices)print(f"Total: {total}") # 49.96 # %18 KDV eklevat_rate = 0.18total_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 hatadef 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ırmaprint(total_with_vat == cumulative) # FALSE bazen! # Düşük tutarlarda görünmez ama büyük hacimde:import randombig_sale = [round(random.uniform(0.01, 999.99), 2) for _ in range(1_000_000)] # Yöntem 1total_1 = sum(price * 1.18 for price in big_sale) # Yöntem 2subtotal = 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 dersfrom decimal import Decimal, getcontextgetcontext().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 kullan.
None4. 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ühendislik | float (FP64 yeter) |
| Grafik, oyun | float (FP32 yeter, hız önemli) |
| AI/ML training | float32 / bfloat16 |
| AI/ML inference | float16 / int8 (quantized) |
| Genel hesaplama | float (Python default FP64) |
| İstatistik | float (binlerce eleman birikse bile fsum ile) |
| Para — ödeme, hesap, fatura | Decimal |
| Para — döviz kuru, FX rate | Decimal |
| Para — vergi, KDV | Decimal |
| Para — istatistik (örn ortalama satış) | float yeter (precision dert değil) |
| Tarih/saat | datetime, 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: ve modülleri. Bu derste anlattığım sorunların çözümleri.
DecimalFractionHands-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 aslında Kahan-benzeri kullanıyor.
.sum()Hands-on 3: Logaritmik faktöriyel (büyük sayılarda taşmadan)#
math.factorial(1000)math.lgammaimport 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.
✓ ile güvenli karşılaştırma; vs farkı.
math.iscloserel_tolabs_tol✓ 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 — , , , scientific notation.
{:.2f}{:,.0f}{:.2%}✓ modülü — sabitler, trig, exp/log, fsum (Kahan-Babuška).
math✓ 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: sayılar. Java/C++'da ekstra kütüphane gerekirken Python'da built-in. syntax, kutupsal koordinatlar, 2D rotation matrisi yerine complex çarpma, sinyal işleme'de neden temel — Hilbert/Fourier dersine uzanan köprü.
complex3 + 4jSı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
Modül 1: Giriş ve Kurulum
Python Nedir, Neden Bu Kadar Popüler?
Öğrenmeye BaşlaModül 1: Giriş ve Kurulum
Python Sürümlerinin Tarihi: 2'den 3.14'e, AI Winter'lardan 'No-GIL' Devrimine
Öğrenmeye BaşlaModül 1: Giriş ve Kurulum