Kesirli Sayılar
Tamsayıların ve aritmetik işlemlerin oldukça kolay olduklarını, buna rağmen yapılarından kaynaklanan taşma ve kırpılma gibi özellikleri olduğunu gördük.
Bu bölümde de biraz ayrıntıya girmek zorundayız. Eğer aşağıdaki listedeki herşeyi bildiğinizi düşünüyorsanız, ayrıntılı bilgileri okumayıp doğrudan problemlere geçebilirsiniz:
- Bin kere 0.001 eklemek 1 eklemekle aynı şey değildir
==veya!=mantıksal ifadelerini kesirli sayı türleriyle kullanmak çoğu durumda hatalıdır- Kesirli sayıların ilk değerleri 0 değil,
.nan'dır;.nandeğeriyle işlem yapılamaz; başka bir değerle karşılaştırıldığında.nanne küçüktür ne de büyük - Üstten taşma değeri
.infinity, alttan taşma değeri-.infinity'dir
Kesirli sayı türleri çok daha kullanışlıdırlar ama onların da mutlaka bilinmesi gereken özellikleri vardır. Kırpılma konusunda çok iyidirler, çünkü zaten özellikle virgülden sonrası için tasarlanmışlardır. Belirli sayıda bitle sınırlı oldukları için taşma bu türlerde de vardır ancak alabildikleri değer aralığı tamsayılarla karşılaştırıldığında olağanüstü geniştir. Ek olarak, tamsayı türlerinin taşma durumunda sessiz kalmalarının aksine, kesirli sayılar "sonsuzluk" değerini alırlar.
Önce kesirli sayı türlerini hatırlayalım:
| Tür | Bit Uzunluğu | İlk Değeri |
|---|---|---|
| float | 32 | float.nan |
| double | 64 | double.nan |
real |
en az 64, veya donanım sağlıyorsa daha fazla (örneğin 80) |
real.nan |
Kesirli tür nitelikleri
Kesirli türlerin nitelikleri tamsayılardan daha fazladır:
.stringoftürün okunaklı ismidir.sizeoftürün bayt olarak uzunluğudur; türün kaç bitten oluştuğunu hesaplamak için bu değeri bir bayttaki bit sayısı olan 8 ile çarpmak gerekir.max"en çok" anlamına gelen "maximum"un kısaltmasıdır; türün alabileceği en büyük değerdir; bu değerin eksi işaretlisi de türün alabileceği en düşük değer olur.min_normal"ifade edebildiği sıfıra en yakın normalize değer" anlamındadır (tür aslında bundan daha küçük değerler de ifade edebilir ama o değerlerin duyarlığı türün normal duyarlığının altındadır).dig"basamak sayısı" anlamına gelen "digits"in kısaltmasıdır; türün kaç basamak duyarlığı olduğunu belirtir.infinity"sonsuz" anlamına gelir; taşma durumunda kullanılan değerdir
Not: "Türün alabileceği en küçük değer", .min değildir; .max'ın eksi işaretlisidir: örneğin -double.max.
Diğer niteliklere bu aşamada gerek olduğunu düşünmüyorum; bütün nitelikleri Properties for Floating Point Types başlığı altında bulabilirsiniz.
Yukarıdaki nitelikleri, birbirleriyle olan ilişkilerini görmek için bir sayı çizgisine şöyle yerleştirebiliriz:
+ +-----------+------------+ .. + .. +----------+----------+ +
| -max -1 -min 0 min 1 max |
-infinity infinity
Yukarıdaki çizginin ölçeğinin doğru olduğunu vurgulamak istiyorum: min ile 1 arasında ne kadar değer ifade edilebiliyorsa, 1 ile max arasında da aynı sayıda değer ifade edilir. Bu da, min ile 1 arasındaki değerlerin son derece yüksek doğrulukta oldukları anlamına gelir. (Aynı durum eksi taraf için de geçerlidir.)
.nan
Açıkça ilk değeri verilmeyen kesirli sayıların ilk değerlerinin .nan olduğunu gördük. .nan değeri bazı anlamsız işlemler sonucunda da ortaya çıkabilir. Örneğin şu programdaki ifadelerin hepsi .nan sonucunu verir:
import std.stdio; void main() { real sıfır = 0; real sonsuz = real.infinity; writeln("nan kullanan her işlem: ", real.nan + 1); writeln("sıfır bölü sıfır : ", sıfır / sıfır); writeln("sıfır kere sonsuz : ", sıfır * sonsuz); writeln("sonsuz bölü sonsuz : ", sonsuz / sonsuz); writeln("sonsuz - sonsuz : ", sonsuz - sonsuz); }
Kesirli sayıların yazımları
Bu üç türün niteliklerine bakmadan önce kesirli sayıların nasıl yazıldıklarını görelim. Kesirli sayıları 123 gibi tamsayı şeklinde veya 12.3 gibi noktalı olarak yazabiliriz.
Ek olarak, 1.23e+4 gibi bir yazımdaki e+, "çarpı 10 üzeri" anlamına gelir. Yani bu örnek 1.23x104'tür, bir başka deyişle "1.23 çarpı 10000"dir ve ifadenin değeri 12300'dür. Eğer e'den sonra gelen değer eksi ise, yani örneğin 5.67e-3 gibi yazılmışsa, o zaman "10 üzeri o kadar değere bölünecek" demektir. Yani bu örnek 5.67/103'tür, bir başka deyişle "5.67 bölü 1000"dir ve ifadenin değeri 0.00567'dir.
Kesirli sayıların bu gösterimlerini, türlerin niteliklerini yazdıran şu programın çıktısında göreceksiniz:
import std.stdio; void main() { writeln("Tür ismi : ", float.stringof); writeln("Duyarlık : ", float.dig); writeln("En küçük normalize değeri: ", float.min_normal); writeln("En büyük değeri : ", float.max); writeln(); writeln("Tür ismi : ", double.stringof); writeln("Duyarlık : ", double.dig); writeln("En küçük normalize değeri: ", double.min_normal); writeln("En büyük değeri : ", double.max); writeln(); writeln("Tür ismi : ", real.stringof); writeln("Duyarlık : ", real.dig); writeln("En küçük normalize değeri: ", real.min_normal); writeln("En büyük değeri : ", real.max); }
Benim ortamımdaki çıktısı aşağıdaki gibi oldu; real türü donanıma bağlı olduğu için bu çıktı sizin ortamınızda farklı olabilir:
Tür ismi : float Duyarlık : 6 En küçük normalize değeri: 1.17549e-38 En büyük değeri : 3.40282e+38 Tür ismi : double Duyarlık : 15 En küçük normalize değeri: 2.22507e-308 En büyük değeri : 1.79769e+308 Tür ismi : real Duyarlık : 18 En küçük normalize değeri: 3.3621e-4932 En büyük değeri : 1.18973e+4932
Gözlemler
ulong türünün tutabileceği en yüksek değerin ne kadar çok basamağı olduğunu hatırlıyor musunuz: 18,446,744,073,709,551,616 sayısı 20 basamaktan oluşur. Buna karşın, en küçük kesirli sayı türü olan float'un bile tutabileceği en yüksek değer 1038 mertebesindedir. Yani şunun gibi bir değer: 340,282,000,000,000,000,000,000,000,000,000,000,000.
real'in en büyük değeri ise 104932 mertebesinde. Yani 4900'den fazla basamağı olan bir sayı!
Başka bir gözlem olarak double'ın 15 duyarlıkla ifade edebileceği en düşük değere bakalım: 0.000...burada 300 tane daha 0 var...0000222507.
Taşma gözardı edilmez
Ne kadar büyük değerler tutuyor olsalar da kesirli sayılarda da taşma olabilir. Kesirli sayı türlerinin iyi tarafı, taşma oluştuğunda tamsayılardaki taşmanın tersine bundan haberimizin olabilmesidir: taşan sayının değeri "artı sonsuz" için .infinity, "eksi sonsuz" için -.infinity haline gelir. Bunu görmek için şu programda .max'ın değerini %10 arttırmaya çalışalım. Sayı zaten en büyük değerinde olduğu için, %10 arttırınca taşacak ve yarıya bölünse bile değeri "sonsuz" olacaktır:
import std.stdio; void main() { real sayı = real.max; writeln("Önce: ", sayı); // 1.1 ile çarpmak, %110 haline getirmektir: sayı *= 1.1; writeln("%10 arttırınca: ", sayı); // İkiye bölerek küçültmeye çalışalım: sayı /= 2; writeln("Yarıya bölünce: ", sayı); }
O programda sayı bir kere real.infinity değerini alınca yarıya bölünse bile sonsuz değerinde kalır:
Önce: 1.18973e+4932 %10 arttırınca: inf Yarıya bölünce: inf
Duyarlık (Hassasiyet)
Duyarlık, yine günlük hayatta çok karşılaştığımız ama fazla sözünü etmediğimiz bir kavramdır. Duyarlık, bir değeri belirtirken kullandığımız basamak sayısıdır. Örneğin 100 liranın üçte birinin 33 lira olduğunu söylersek, duyarlık 2 basamaktır; çünkü 33 değeri iki basamakla belirtilmiştir. Daha hassas bir değer gereken bir durumda 33.33 dersek, duyarlık 4 basamaktır...
Kesirli sayı türlerinin bit olarak uzunlukları yalnızca alabilecekleri en düşük ve en yüksek değerleri değil; değerlerin duyarlıklarını da etkiler. Bit olarak uzunlukları ne kadar fazlaysa, duyarlıkları da o kadar fazladır.
Kırpılma yoktur
Kesirli sayılarda virgülden sonrasını atmak anlamında kırpılma yoktur. Sayının doğruluğu duyarlığıyla ilgilidir: virgülden sonraki basamakların ne kadar doğru olduklarını duyarlık belirler.
Hangi durumda hangi tür
Özel bir neden yoksa her zaman için real'i kullanmak doğruluk açısından yararlıdır. float'un duyarlığı çok düşüktür, ama küçük olmasının yarar sağlayacağı nadir programlardan birisini yazıyorsanız, o zaman düşünerek ve ölçerek karar verebilirsiniz. Öte yandan real'in duyarlığı her ortamda aynı olmadığı için, taşınabilirlik açısından double kullanmak isteyebilirsiniz.
Her değeri ifade etmek olanaksızdır
Her değerin ifade edilememesi kavramını önce günlük hayatımızda göstermek istiyorum. Kullandığımız onlu sayı sisteminde virgülden önceki basamaklar birler, onlar, yüzler, vs. basamaklarıdır; virgülden sonrakiler de onda birler, yüzde birler, binde birler, vs...
Eğer ifade etmek istediğimiz değer bu basamakların bir karışımı ise, değeri tam olarak ifade edebiliriz. Örneğin 0.23 değeri 2 adet onda bir değerinden ve 3 adet yüzde bir değerinden oluştuğu için tam olarak ifade edilebilir. Öte yandan, 1/3 değerini onlu sistemimizde tam olarak ifade edemeyiz çünkü virgülden sonra ne kadar uzatırsak uzatalım yeterli olmaz: 0.33333...
Benzer durum kesirli sayılarda da vardır. Türlerin bit sayıları sınırlı olduğu için, her değer tam olarak ifade edilemez.
Bilgisayarlarda kullanılan ikili sayı sistemlerinin bir farkı, virgülden öncesinin birler, ikiler, dörtler, vs. diye; virgülden sonrasının da yarımlar, dörtte birler, sekizde birler, vs. diye gitmesidir. Eğer değer bunların bir karışımı ise tam olarak ifade edilebilir; değilse edilemez.
Bilgisayarlarda tam olarak ifade edilemeyen bir değer 0.1'dir (10 kuruş gibi). Onlu sistemde tam olarak 0.1 şeklinde ifade edilebilen bu değer, ikili sistemde 0.0001100110011... diye tekrarlar ve kesirli sayının duyarlığına bağlı olarak belirli bir yerden sonra hatalıdır. (Tekrarladığını söylediğim o son sayıyı ikili sistemde yazdım, onlu değil...)
Bunu gösteren aşağıdaki örneği ilginç bulabilirsiniz. Bir değişkenin değerini bir döngü içinde her seferinde 0.001 arttıralım. Döngünün 1000 kere tekrarlanmasının ardından sonucun 1 olmasını bekleriz. Oysa öyle çıkmaz:
import std.stdio; void main() { float sonuç = 0; // Bu döngünün 1000 kere tekrarlandıktan sonra 1 değerine // ulaşacağını düşünürüz: while (sonuç < 1) { sonuç += 0.001; } // Bakalım doğru mu... if (sonuç == 1) { writeln("Beklendiği gibi 1"); } else { writeln("FARKLI: ", sonuç); } }
FARKLI: 1.00099
Bunun nedeni; 0.001 değerinin de tam olarak ifade edilemeyen bir değer olması, ve bu değerdeki hata miktarının sonucu 1000 kere etkilemesidir. Sonuçtan da anlaşılacağı gibi, döngüden çıkılabilmesi için 1001 kere tekrarlanması gerekmiştir.
Kesirli sayı karşılaştırmaları
Tamsayılarda şu karşılaştırma işleçlerini kullanıyorduk: eşitlik (==), eşit olmama (!=), küçüklük (<), büyüklük (>), küçük veya eşit olma (<=), büyük veya eşit olma (>=).
Kesirli sayılarda geçersiz değeri gösteren .nan da bulunduğu için, onun diğer değerlerle küçük büyük olarak karşılaştırılması anlamsızdır. Örneğin .nan'ın mı yoksa 1'in mi daha büyük olduğu gibi bir soru yanıtlanamaz.
Bu yüzden kesirli sayılarda başka bir karşılaştırma kavramı daha vardır: sırasızlık. Sırasızlık, değerlerden en az birisinin .nan olması demektir.
Aşağıdaki tablo kesirli sayı karşılaştırma işleçlerini gösteriyor. İşleçlerin hepsi ikilidir ve örneğin soldaki == sağdaki şeklinde kullanılır. false ve true içeren sütunlar, işleçlerin hangi durumda ne sonuç verdiğini gösterir.
Sonuncu sütun, ifadelerden birisinin .nan olması durumunda o işlecin kullanımının anlamlı olup olmadığını gösterir. Örneğin 1.2 < real.nan ifadesinin sonucu false çıksa bile, ifadelerden birisi real.nan olduğu için bu sonucun bir anlamı yoktur çünkü bunun tersi olan real.nan < 1.2 ifadesi de false verir.
İşleç |
Anlamı |
Soldaki Büyükse |
Soldaki Küçükse |
İkisi Eşitse |
En Az Birisi .nan ise |
.nan ile Anlamlı |
|---|---|---|---|---|---|---|
| == | eşittir | false | false | true | false | evet |
| != | eşit değildir | true | true | false | true | evet |
| > | büyüktür | true | false | false | false | hayır |
| >= | büyüktür veya eşittir | true | false | true | false | hayır |
| < | küçüktür | false | true | false | false | hayır |
| <= | küçüktür veya eşittir | false | true | true | false | hayır |
| !<>= | küçük, büyük, eşit değildir | false | false | false | true | evet |
| <> | küçüktür veya büyüktür | true | true | false | false | hayır |
| <>= | küçüktür, büyüktür, veya eşittir | true | true | true | false | hayır |
| !<= | küçük değildir ve eşit değildir | true | false | false | true | evet |
| !< | küçük değildir | true | false | true | true | evet |
| !>= | büyük değildir ve eşit değildir | false | true | false | true | evet |
| !> | büyük değildir | false | true | true | true | evet |
| !<> | küçük değildir ve büyük değildir | false | false | true | true | evet |
Dikkat ederseniz; ! karakteri içeren, yani anlamında "değildir" sözü bulunan bütün işleçlerin .nan ile kullanılması anlamlıdır ve sonuç hep true'dur. .nan'ın geçerli bir değeri göstermiyor olması, çoğu karşılaştırmaya "değil" sonucunu verdirir.
Problemler
- Önceki bölümdeki hesap makinesini kesirli bir tür kullanacak şekilde değiştirin. Böylece hesap makineniz çok daha doğru sonuçlar verecektir. Denerken değerleri girmek için 1000, 1.23, veya 1.23e4 şeklinde yazabilirsiniz.
- Girişten 5 tane kesirli sayı alan bir program yazın. Bu sayıların önce iki katlarını yazsın, sonra da beşe bölümlerini. Bu problemi bir sonra anlatılacak olan dizilere hazırlık olarak soruyorum. Eğer bu programı şimdiye kadar öğrendiklerinizle yazarsanız, dizileri anlamanız daha kolay olacak.
D.ershane
Forum
Wiki
Projeler
Tanıtım
İletişim
Hakları