Büyük Sayı Algoritmaları - Tamsayılarda Dört İşlem
Daha önceki yazılarda, doğal sayılarda dört işlem algoritmalarına değinmiştik. Bu yazıda, önceden yazdığımız alt seviye fonksiyonları kullanarak, tam sayılar üzerinde dört işlem gerçekleştiren fonksiyonları kodlayacağız.
Projenin bu yazıya kadar olan kısmını Github üzerinden indirebilirsiniz
Tamsayılar üzerinde toplama yaparken, dört farklı durumla karşılaşabiliriz.
- Sayıların ikisi de pozitif: Doğal sayılarda toplama işlemi yapacağız.
- Sayılardan biri negatif, negatif olanın mutlak değeri küçük: Doğal sayılarda çıkarma yapacağız.
- Sayılardan biri negatif, negatif olanın mutlak değeri büyük: Negatif sayının mutlak değerinden, diğer sayıyı çıkarıp, sonucu negatif olarak ayarlayacağız.
- Sayıların ikisi de negatif: Sayıların mutlak değerlerini toplayıp, sonucu negatif olarak ayarlayacağız.
Bu dört durumu, aşağıdaki gibi iki duruma sadeleştirmek mümkün.
- Sayıların İşareti Aynı: Sayıların mutlak değerlerini topla, aynı işareti kullan.
- Sayıların İşareti Farklı: İlk sayının mutlak değerinden, ikinci sayının mutlak değerini çıkar. Eğer sonuç pozitif ise, ilk sayının işaretini kullan, değilse, ilk sayının işaretinin zıttını kullan.
Bu algoritmayı kodlayabilmek için, mutlak değerler üzerinde toplama çıkarma yapan fonksiyonlara ihtiyacımız olacak.
Eğer argümanlardan ilki, diğerinden kısa ise, işaretçilerin yerlerini
değiştiriyoruz, çünkü, bn_add_nn
fonksiyonu ilk argümanın daha kısa
olmadığını varsayıyor. Fonksiyondan, sonucun hane sayısını döndürüyoruz.
İki sayının mutlak değerleri arasında çıkarma yaparken, hangisinin mutlak
değerinin daha büyük olduğunu kontrol etmeliyiz. Bunun için, daha önce tanımladığımız
bn_cmp_nn
fonksiyonunu kullanacağız. Burada,
bn_cmp_nn
fonksiyonuna argüman olarak, sayıların uzunluklarının mutlak
değerlerini göndermemiz gerekiyor. Böylece, mutlak değerler arasında karşılaştırma
yapılmış oluyor. Karşılaştırma yaptıktan sonra, eğer birinci sayının büyük
olduğunu tespit edersek, birinciden ikinciyi çıkarıp, pozitif uzunluk
döndüreceğiz. Aksi takdirde, ikinciden birinciyi çıkarıp, negatif uzunluk
döndüreceğiz. Sayıların eşit olması durumunda, sıfır uzunluk döndürmemiz yeterli.
Yukarıdaki fonksiyonda, bn_sub_n1
ve bn_sub_nn
fonksiyonları kullandık,
ancak, bunları daha önce kodlamamıştık. Yeri gelmişken, onları da kodlayalım.
bnz_add_abs
ve bnz_sub_abs
fonksiyonlarını kullanarak, bn_add
fonksiyonunu aşağıdaki gibi yazabiliriz;
Bu fonksiyonda nispeten ilginç olan tek şey, op1->length ^ op2->length
ifadesi.
Bunu, sayıların işaretlerini karşılaştırmak için kullanıyoruz. Bu ifade, eğer
sayıların işaretleri aynı ise pozitif, değil ise negatif sonuç döndürecek. Binary xor
kurallarını ve 2'nin tümleyeni düzenini düşünürseniz,
op1->length ^ op2->length
ifadesini anlamanız kolay olacaktır.
Çıkarma fonksiyonu da aşağıdaki gibi kodlanabilir;
Bu fonksiyonun toplamadan tek farkı, işaretler aynı olduğunda çıkarma, farklı olduğunda toplama yapılıyor olması.
Tamsayılarda çarpma işleminin mantığı, toplama çıkarmaya göre daha kolay. Doğal sayılarda çarpma işlemi yaptıktan sonra, işleçlerin işaretleri aynı ise sonucu pozitif, farklı ise negatif yapacağız.
Kısaca birkaç noktaya değineyim;
Burada, işleçlerden biri sıfırsa, başka bir işlem yapmadan, sonucu sıfıra ayarlıyoruz.
|
bn_size_t len = op1_len + op2_len;
|
|
bn_digit_t *rp;
|
|
if (result->digits == op1->digits || result->digits == op2->digits)
|
|
{
|
|
rp = bn_xmalloc(len);
|
|
}
|
|
else
|
|
{
|
|
rp = BN_GROW(result, len);
|
|
}
|
İşleçlerin biri, aynı zamanda sonuç olarak verilmiş olabilir (Örn. bnz_mul(a, a, b) -> a = a x b). Bunu test etmek için, işaretçileri karşılaştırıyoruz. Eğer çakışma tespit edersek, hafızada yeni yer açmak zorundayız. Aksi halde, var olan alanı kullanacağız.
Eğer başlangıçta hafızada yeni bir yer açmış isek, result
'ı bu alanı kullanacak şekilde ayarlamak gerekiyor.
Fonksiyonun geri kalanında yeni birşey yok.
Bölme işlemine geçmeden önce, bölmenin sonucu tam sayı olmadığında, nasıl yuvarlayacağımıza karar vermemiz gerekiyor. Bu konuda 3 farklı standart var.
- Sıfıra Doğru Yuvarlama: Bu yöntemde, sayının noktadan sonraki kısmını siliyoruz.
- Pozitif Sonsuza Yuvarlama: Bu yöntemde, sayıyı yukarıya yuvarlıyoruz.
- Negatif Sonsuza Yuvarlama: Bu yöntemde, sayıyı aşağıya yuvarlıyoruz.
Bu yazı için, sıfıra doğru yuvarlama yöntemini seçeceğim. Aşağıdaki tabloda, farklı bölen/bölünenler için, bölüm ve kalanın olması gerektiği değerleri görebilirsiniz.
+---------+-------+-------+-------+ | Bölünen | Bölen | Bölüm | Kalan | +---------+-------+-------+-------+ | 3 | 2 | 1 | 1 | +---------+-------+-------+-------+ | 3 | -2 | -1 | 1 | +---------+-------+-------+-------+ | -3 | 2 | -1 | -1 | +---------+-------+-------+-------+ | -3 | -2 | 1 | -1 | +---------+-------+-------+-------+
Kodlarımız da aşağıdaki gibi;
Yukarıdaki fonksiyon uzun gorunse de, karmasik birşey yok. Açıklayıcı notları satır aralarında bulabilirsiniz.
Bu yazı ile, kütüphanenin temel kısımlarını bitirmiş olduk. Bir sonraki yazıda, tam sayılardaki dört işlem fonksiyonları için test yazacağız.