Sayılar neden bu kadar önemli?
Ilgaz Bank'ta bir mevduat hesabının faizi, Anadolu Market'te bir sepetin indirimli tutarı, bir kargonun kaç kutuya sığacağı... Hepsi sayı işlemi. Python'ın aritmetiği hesap makinesine benzer ama birkaç yerde sürpriz yapar: bölme hep ondalıklı sonuç verir, ondalıklı sayılar bazen "yanlış" görünür ve yuvarlama sandığın gibi çalışmaz. Bu derste bu sürprizlerin hepsini önceden göreceksin.
Temel operatörler
bakiye = 10000 print(bakiye + 2500) # toplama print(bakiye - 750) # çıkarma print(bakiye * 1.05) # çarpma print(bakiye / 3) # bölme: her zaman float print(bakiye // 3) # tam bölme: ondalık atılır print(bakiye % 3) # kalan (mod) print(2 ** 10) # üs alma
En çok dikkat isteyen üçlü: / her zaman float döndürür, 10 / 2 bile 5.0'dır. // tam bölme (floor division) aşağı yuvarlanmış tam sonucu verir. % ise kalanı (modulo). Bu ikili birlikte çok iş görür: 10000 TL'yi 3 taksite bölersen taksit başına 3333 TL düşer, 1 TL artar.
| Operatör | Anlamı | Örnek | Sonuç |
|---|---|---|---|
+ - * | Toplama, çıkarma, çarpma | 7 * 3 | 21 |
/ | Bölme (hep float) | 7 / 2 | 3.5 |
// | Tam bölme | 7 // 2 | 3 |
% | Kalan | 7 % 2 | 1 |
** | Üs | 2 ** 3 | 8 |
-x | İşaret değiştirme | -(5 - 8) | 3 |
İşlem önceliği matematikteki gibidir: önce parantez, sonra üs, sonra çarpma-bölme-kalan, en son toplama-çıkarma. Emin değilsen parantez kullan; parantez ücretsizdir ve okuyana yardım eder. Bir de kısayol atamalar var: bakiye += 500, bakiye = bakiye + 500 demektir. Aynı şekilde -=, *=, /=, //=, %= kullanılır.
Tam bölme ve kalanla gerçek problemler
Anadolu Market deposunda 250 ürün var, her kutuya 12 ürün sığıyor. Kaç tam kutu dolar, kaç ürün açıkta kalır, toplam kaç kutu gerekir?
urun = 250
kutu_kapasitesi = 12
tam_kutu = urun // kutu_kapasitesi
artan = urun % kutu_kapasitesi
print("Tam kutu:", tam_kutu)
print("Açıkta kalan:", artan)
gereken_kutu = tam_kutu + (1 if artan else 0)
print("Gereken kutu:", gereken_kutu)
print("Çift mi?", 250 % 2 == 0)
Son satır, kalan operatörünün klasik kullanımı: bir sayının 2'ye bölümünden kalan 0 ise sayı çifttir. Aynı mantıkla "her 100. sipariş hediye" kuralı siparis_no % 100 == 0 ile yazılır. divmod(250, 12) fonksiyonu ise tam bölüm ve kalanı tek seferde verir: (20, 10).
Kayan nokta hatası
Şimdi sürpriz. Şu kodu çalıştır:
print(0.1 + 0.2) print(0.1 + 0.2 == 0.3) print(1499.90 * 3) print(round(0.1 + 0.2, 2)) print(round(2.675, 2), round(2.5), round(3.5))
0.1 + 0.2, 0.30000000000000004 çıkar. Bu Python'ın hatası değil; bilgisayarlar sayıları ikilik tabanda saklar ve 0.1, ikilik tabanda sonsuz basamaklı bir sayıdır (ondalıkta 1/3'ün 0.333... olması gibi). Her dil aynı sonucu verir. Sonuçları yuvarlamak için round(x, basamak) kullanırsın. Ancak round'un kendisi de sürprizli: Python "bankacı yuvarlaması" yapar, tam .5 durumunda en yakın çift sayıya gider; bu yüzden round(2.5) 2, round(3.5) 4 verir. round(2.675, 2) ise 2.67 çıkar çünkü 2.675 bellekte aslında 2.67499... olarak durur.
a == b yerine abs(a - b) < 0.001 gibi bir tolerans kullan ya da sonucu önce yuvarla. Bu uygulamadaki testler de aynı yöntemi kullanır.decimal modülünü kullan. Analiz ve raporlamada float ile round yeterlidir.Yuvarlama seçenekleri bir arada
Aynı sayıyı farklı amaçlarla yuvarlayabilirsin: rapor için iki ondalık, fatura için kuruşa, kutu sayısı için yukarı. Hangi fonksiyonun ne verdiğini yan yana gör:
import math x = 7.456 print(round(x, 1), round(x, 2)) # en yakına print(math.floor(x), math.ceil(x)) # aşağı, yukarı print(int(x)) # ondalığı at print(round(-7.456), math.floor(-7.456), int(-7.456))
Son satır negatif sayılarda farkı gösterir: int sıfıra doğru keser (-7), floor aşağı iner (-8), round en yakını seçer (-7). Bakiye ve borç hesaplarında bu üçünü karıştırmak kuruş farklarına yol açar.
math modülü
Daha fazlası için math modülünü içe aktarırsın. En sık kullanacakların: yukarı yuvarlama ceil, aşağı yuvarlama floor, karekök sqrt, sabit pi.
import math print(math.ceil(250 / 12)) # 21: gereken kutu, tek satırda print(math.floor(7.9)) # 7 print(math.sqrt(144)) # 12.0 print(round(math.pi, 4)) print(abs(-350), max(3, 9, 4), min(3, 9, 4)) print(pow(2, 10), 10 ** 3)
abs, max, min, pow ve round hazır (built-in) fonksiyonlardır, içe aktarmaya gerek yoktur. Kutu problemine geri dönersen: math.ceil(250 / 12) tek satırda "gereken kutu" sayısını verir.
Ilgaz Bank faiz hesabı
Bir müşteri 50.000 TL'yi yıllık %30 faizle 3 yıl vadeli hesaba yatırıyor. Basit faizde her yıl ana para üzerinden faiz işler; bileşik faizde (compound interest) faiz de faiz kazanır. Formül: bileşik tutar = anapara × (1 + oran) üzeri yıl.
anapara = 50_000
oran = 0.30
yil = 3
basit = anapara * (1 + oran * yil)
bilesik = anapara * (1 + oran) ** yil
print("Basit faizle:", round(basit, 2))
print("Bileşik faizle:", round(bilesik, 2))
print("Fark:", round(bilesik - basit, 2))
50_000 yazımına dikkat: alt çizgi, büyük sayıları okunur yapar ve Python onu yok sayar. Üs operatörünün çarpmadan önce hesaplandığını da gör; parantez yalnızca 1 + oran için gerekti.
30 değil 0.30 olarak yazarsın. Kullanıcıdan 30 alıyorsan 100'e bölmeyi unutma; en sık yapılan finans hatalarından biridir.Operatörlerin tamamını (karşılaştırma, mantıksal, bit, öncelik) Operatörler dersinde ayrıca göreceksin. Bu bölüm sayıların kendisine odaklanıyor: yazım biçimleri, özel değerler, güvenilir yuvarlama ve finans hesaplarında gerçekten kullanılan araçlar.
Sayı yazım biçimleri (literal)
Python bir sayıyı birçok biçimde yazmana izin verir; hepsi aynı int ya da float türüne dönüşür:
| Yazım | Değer | Nerede görürsün? |
|---|---|---|
1_250_000 | 1250000 | Büyük tutarlar; alt çizgi yalnız okunurluk için |
1.5e6, 2e-3 | 1500000.0, 0.002 | Bilimsel gösterim; sonuç hep float |
0b1011 | 11 | İkilik taban: izin bayrakları |
0o755 | 493 | Sekizlik: Linux dosya izinleri |
0xFF | 255 | Onaltılık: renk kodları, bellek adresleri |
3 + 4j | complex | Karmaşık sayı: mühendislik hesapları |
print(1_250_000 + 1, 1.5e6, 2e-3)
print(0b1011, 0o755, 0xFF)
print(bin(11), hex(255), oct(493)) # tersine: sayıdan metne
print(int("ff", 16), int("1011", 2)) # metinden, tabanı vererek
z = 3 + 4j
print(z.real, z.imag, abs(z)) # karmaşık sayının büyüklüğü 5.0int(metin, taban) özellikle veri temizlemede işe yarar: renk kodları, donanım kimlikleri ya da bazı banka sistemlerinin onaltılık işlem numaraları bu yolla sayıya çevrilir. Karmaşık sayılar (complex) PCEP sınavında sorulur ama iş hayatında nadirdir; varlığını bilmen yeterli.
Özel float değerleri ve güvenli karşılaştırma
float türünde üç özel değer vardır: artı sonsuz, eksi sonsuz ve "sayı değil" (NaN, not a number). Veri setlerinde eksik değer çoğunlukla NaN olarak gelir; pandas derslerinde onunla sürekli karşılaşacaksın. NaN'ın en garip özelliği kendine bile eşit olmamasıdır.
import math
sonsuz = float("inf")
eksik = float("nan")
print(sonsuz > 10**100, -sonsuz < -10**100)
print(eksik == eksik, math.isnan(eksik)) # NaN kendine eşit değil!
print(math.isinf(sonsuz), math.isfinite(1.5))
print(0.1 + 0.2 == 0.3)
print(math.isclose(0.1 + 0.2, 0.3)) # göreli tolerans
print(math.isclose(1000.004, 1000.0, abs_tol=0.01))
elle = 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1
print(elle, math.fsum([0.1] * 10)) # fsum hata birikimini önlermath.isclose(a, b) kendi tolerans hesabını yapan güvenli karşılaştırmadır; varsayılan göreli tolerans çok küçüktür, tutar karşılaştırmasında abs_tol=0.005 (yarım kuruş) gibi mutlak bir tolerans vermek daha anlamlıdır. math.fsum ise çok sayıda float topladığında biriken küçük hataları telafi eder. (Python 3.12 ile hazır sum da float toplarken benzer bir telafi yapmaya başladı; ama elle + ile biriktirdiğin toplamlarda hata hâlâ birikir.)
Yuvarlama: round, Decimal ve muhasebe kuralı
round fonksiyonu bankacı yuvarlaması (yarımda çifte) yapar ve ikinci parametresi negatif de olabilir: round(1234.5, -2) yüzlere yuvarlar. Ama faturada Türkiye'deki alışılmış kural "yarım ve üstü yukarı"dır (ROUND_HALF_UP). Bunu float ile güvenilir yapamazsın; decimal modülünün quantize metodu tam bu iş içindir.
from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_EVEN
print(round(1234.567, -2), round(1250, -2), round(1350, -2))
fiyat = Decimal("2.675")
kurus = Decimal("0.01")
print(fiyat.quantize(kurus, rounding=ROUND_HALF_UP)) # 2.68: muhasebe
print(fiyat.quantize(kurus, rounding=ROUND_HALF_EVEN)) # 2.68: tam yarım, 8 çift
print(Decimal("2.665").quantize(kurus, rounding=ROUND_HALF_EVEN)) # 2.66
print(round(2.675, 2)) # float: 2.67!
tutar = Decimal("19.99") * 3
kdv = (tutar * Decimal("0.20")).quantize(kurus, rounding=ROUND_HALF_UP)
print(tutar, kdv, tutar + kdv)| Yöntem | 2.665 → | 2.675 → | Ne zaman? |
|---|---|---|---|
round(x, 2) (float) | 2.67* | 2.67* | Analiz, grafik, yaklaşık rapor |
ROUND_HALF_UP | 2.67 | 2.68 | Fatura, makbuz, muhasebe |
ROUND_HALF_EVEN | 2.66 | 2.68 | İstatistik; birikimli yuvarlama hatasını azaltır |
ROUND_DOWN | 2.66 | 2.67 | Kuruş küsuratını kesme (faiz tahakkuku politikasına göre) |
* İkisi de 2.67 çıkar: 2.665 bellekte biraz üstünde, 2.675 biraz altında saklanır. float sonuçları sayının bellekteki ikilik karşılığına bağlıdır; tam yarım gibi görünen değerler aslında biraz altında ya da üstünde olabilir. Decimal ise yazdığın ondalığı tam olarak saklar. Decimal'ı hep metinden oluştur: Decimal(2.675) bozuk float'ı kopyalar.
Decimal("1.5") + 0.1 TypeError verir; Python iki farklı kesinlikteki sayıyı sessizce birleştirmez. Hesaba giren her sabiti Decimal("0.20") gibi metinden oluştur; tam sayılarla (Decimal("19.99") * 3) karıştırmak serbesttir.math ve statistics: sık kullanılan araçlar
| Fonksiyon | Ne yapar? | İş örneği |
|---|---|---|
math.prod(liste) | Çarpım | Ardışık büyüme oranlarını birleştirmek |
math.gcd(a, b), math.lcm(a, b) | EBOB, EKOK | Paket boyutu, ortak sevkiyat günü |
math.log10(x), math.log(x) | Logaritma | Büyüklük mertebesi, sürekli faiz |
math.exp(x) | e üzeri x | Sürekli bileşik faiz |
math.comb(n, k) | Kombinasyon | Kaç farklı kampanya paketi kurulabilir? |
statistics.mean / median | Ortalama, ortanca | Sepet tutarı özeti (aykırı değere dayanıklı ortanca) |
import math, statistics
print(math.lcm(6, 8), math.gcd(48, 36)) # 24. günde iki tır aynı gün; 12'li paket
print(round(math.prod([1.10, 1.05, 0.97]), 4)) # üç dönemin toplam büyüme çarpanı
print(math.comb(10, 3)) # 10 üründen 3'lü paket sayısı
sepetler = [120, 135, 98, 110, 4200] # bir kurumsal sipariş aykırı
print(statistics.mean(sepetler), statistics.median(sepetler))
eski, yeni = 84_000, 96_600
print(f"Değişim: %{(yeni - eski) / eski * 100:.1f}")Son iki satır, iş hayatının en sık formülünü gösteriyor: yüzde değişim = (yeni − eski) / eski × 100. Eski değer sıfırsa formül tanımsızdır; raporda "yeni" ya da "—" yazmak gerekir. Ortalama ile ortanca arasındaki büyük fark ise verinde aykırı değer olduğunun işaretidir: tek bir kurumsal sipariş "ortalama sepeti" bir anda on katına çıkarabilir.
Decimal ve Fraction
from decimal import Decimal ile ondalık sayıları tam olarak temsil edersin: Decimal("0.1") + Decimal("0.2") tam 0.3 verir. Dikkat, Decimal'ı metinden oluşturmalısın; Decimal(0.1) zaten bozuk float'ı alır. Muhasebe, faturalama ve faiz hesapları Decimal ister. fractions.Fraction ise kesirleri tam tutar: 1/3 + 1/3, 2/3 çıkar. Analizde nadir, bilimsel hesapta değerlidir.
Tam sayı bölmede negatif sayılar
-7 // 2 sonucu -4'tür, -3 değil. Python tam bölmeyi her zaman aşağı (eksi sonsuza) yuvarlar ve % işareti bölenin işaretini alır: -7 % 2 1 verir. Bu tutarlılık takvim hesaplarında (haftanın günü, ay döngüsü) işe yarar, ama C ya da JavaScript'ten gelenleri şaşırtır.
Büyük sayılar ve performans
Python tam sayıları sınırsızdır: 2 ** 1000 sorunsuz hesaplanır. Bu rahatlığın bedeli hızdır; milyonlarca sayıyla işlem yapacaksan numpy'nin sabit boyutlu sayı tipleri çok daha hızlıdır. İleri seviyede bu geçişi yapacaksın.