Forum: Ders Arası RSS
struct Kesir
acehreli (Moderatör) #1
Kullanıcı başlığı: Ali Çehreli
Üye Haz 2009 tarihinden beri · 4527 mesaj
Grup üyelikleri: Genel Moderatörler, Üyeler
Profili göster · Bu konuya bağlantı
Konu adı: struct Kesir
İşleç Yükleme bölümünü İngilizceleştirirken şimdiki çok kötü problemlerini de çıkartmaya karar verdim:

  http://ddili.org/ders/d/islec_yukleme.html

Onun yerine bir kaç gün sonra aşağıdaki problemi (ve tabii çözümünü) koyacağım. Belki kendiniz şimdiden denemek istersiniz:

Problem

    Payını ve paydasını long türünde iki üye olarak tutan bir kesirli sayı türü tanımlayın. Böyle bir yapının bir yararı, float, double, ve real'deki değer kayıplarının bulunmamasıdır. Örneğin, 1.0/3 gibi bir double değerin 3 ile çarpılmasının sonucu 1.0 olmadığı halde 1⁄3'ü temsil eden Kesirli bir nesnenin 3 ile çarpılmasının sonucu tam olarak 1'dir:

    struct Kesir
    {
        long pay;
        long payda;
 
        /* Kurucu işlev kolaylık olsun diye paydanın
         * belirtilmesini gerektirmiyor ve 1 varsayıyor. */
        this(long pay, long payda = 1)
        {
            enforce(payda != 0, "Payda sıfır olamaz");
 
            this.pay = pay;
            this.payda = payda;
 
            /* Paydanın eksi değer almasını başından önlemek daha
             * sonraki işlemleri basitleştirecek. */
            if (this.payda < 0) {
                this.pay = -this.pay;
                this.payda = -this.payda;
            }
        }
 
        /* ... işleçleri siz tanımlayın ... */
    }

    Bu yapı için işleçler tanımlayarak olabildiğince temel türler gibi işlemesini sağlayın. Yapının tanımı tamamlandığında aşağıdaki birim testi bloğu hatasız işletilebilsin. O blokta şu işlemler bulunuyor:

  • Payda sıfır olduğunda hata atılıyor. (Bu, yukarıdaki kurucudaki enforce ile zaten sağlanıyor.)

  • Değerin eksi işaretlisini üretmek: Örneğin, 1⁄3 değerinin eksilisi olarak -1⁄3 değeri elde ediliyor.

  • ++ ve -- ile değer bir arttırılıyor veya azaltılıyor.

  • Dört işlem destekleniyor: Hem +=, -=, *=, ve /= ile tek nesnenin değeri değiştirilebiliyor hem de iki nesne +, -, *, ve / aritmetik işlemlerinde kullanılabiliyor. (Kurucuda olduğu gibi, sıfıra bölme işlemi de denetlenmeli ve önlenmelidir.)

  • Hatırlatma olarak, a⁄b ve c⁄d gibi iki kesirli arasındaki aritmetik işlem formülleri şöyledir:

    • Toplama formülü: a⁄b + c⁄d = (a*d + c*b)⁄(b*d)
    • Çıkarma formülü: a⁄b - c⁄d = (a*d - c*b)⁄(b*d)
    • Çarpma formülü: a⁄b * c⁄d = (a*c)⁄(b*d)
    • Bölme formülü: (a⁄b) / (c⁄d) = (a*d)⁄(b*c)

  • Nesnenin değeri gerektiğinde double'a dönüştürülebiliyor.

  • Sıralama ve eşitlik karşılaştırmaları pay ve paydaların tam değerlerine göre değil, o üyelerin ifade ettikleri değerlere göre uygulanıyorlar. Örneğin 1⁄3 ve 20⁄60 kesirli değerleri eşit kabul ediliyorlar.

unittest
{
    /* Payda 0 olduğunda hata atılmalı. */
    {
        auto hataAtıldı_mı = false;
 
        try {
            auto b = Kesir(42, 0);
 
        } catch (Exception hata) {
            hataAtıldı_mı = true;
        }
 
        assert(hataAtıldı_mı);
    }
 
    /* 1⁄3 değeriyle başlayacağız. */
    auto a = Kesir(1, 3);
 
    /* -1⁄3 */
    assert(-a == Kesir(-1, 3));
 
    /* 1⁄3 + 1 == 4⁄3 */
    ++a;
    assert(a == Kesir(4, 3));
 
    /* 4⁄3 - 1 == 1⁄3 */
    --a;
    assert(a == Kesir(1, 3));
 
    /* 1⁄3 + 2⁄3 == 3⁄3 */
    a += Kesir(2, 3);
    assert(a == Kesir(1));
 
    /* 3⁄3 - 2⁄3 == 1⁄3 */
    a -= Kesir(2, 3);
    assert(a == Kesir(1, 3));
 
    /* 1⁄3 * 8 == 8⁄3 */
    a *= Kesir(8);
    assert(a == Kesir(8, 3));
 
    /* 8⁄3 / 16⁄9 == 3⁄2 */
    a /= Kesir(16, 9);
    assert(a == Kesir(3, 2));
 
    /* 1.5 + 2.5 == 4 */
    assert(a + Kesir(5, 2) == Kesir(4, 1));
 
    /* 1.5 - 0.75 == 0.75 */
    assert(a - Kesir(3, 4) == Kesir(3, 4));
 
    /* 1.5 * 10 == 15 */
    assert(a * Kesir(10) == Kesir(15, 1));
 
    /* 1.5 / 4 == 3⁄8 */
    assert(a / Kesir(4) == Kesir(3, 8));
 
    /* Sıfırla bölmek hata atmalı. */
    {
        auto hataAtıldı_mı = false;
 
        try {
            auto sonuç = Kesir(42, 1) / Kesir(0);
 
        } catch (Exception hata) {
            hataAtıldı_mı = true;
        }
 
        assert(hataAtıldı_mı);
    }
 
    assert(to!double(a) == 1.5);
 
    /* Payı az olan öncedir. */
    assert(Kesir(3, 5) < Kesir(4, 5));
 
    /* Paydası büyük olan öncedir. */
    assert(Kesir(3, 9) < Kesir(3, 8));
    assert(Kesir(1, 1_000) > Kesir(1, 10_000));
 
    /* Değeri küçük olan öncedir. */
    assert(Kesir(10, 100) < Kesir(1, 2));
 
    /* Eksi değer öncedir. */
    assert(Kesir(-1, 2) < Kesir(0));
    assert(Kesir(1, -2) < Kesir(0));
 
    /* Aynı değerler hem <= hem de >= olmalı.  */
    assert(Kesir(-1, -2) <= Kesir(1, 2));
    assert(Kesir(1, 2) <= Kesir(-1, -2));
    assert(Kesir(3, 7) <= Kesir(9, 21));
    assert(Kesir(3, 7) >= Kesir(9, 21));
 
    /* Değerleri aynı olanlar eşit olmalı. */
    assert(Kesir(1, 3) == Kesir(20, 60));
 
    /* Karışık işaretler aynı sonucu üretmeli. */
    assert(Kesir(-1, 2) == Kesir(1, -2));
    assert(Kesir(1, 2) == Kesir(-1, -2));
}

Ali
Avatar
Salih Dinçer #2
Üye Ock 2012 tarihinden beri · 1912 mesaj · Konum: İstanbul
Grup üyelikleri: Üyeler
Profili göster · Bu konuya bağlantı
Peki Kesir(4, 2) ile Kesir(5, 3)'ü toplamak için payda eşitleme ve sadeleştirme algoritmasından mı geçireceğiz?

Ayrıca paydaları eşit olan (örneğin 1/3 + 2/3) olan sayıyı, paydaları eşit olduğu için önce payları toplayıp paydaları dokunmamak mümkün ancak doğru bir kesiri göstermediği için düzenli olarak her işlemi sadeleştirme algoritmasından geçirmek ve/veya (this.pay == this.payda ? this.pay = 1; this.payda = 1;) demek gerekecek.

Offf karışık bir şey bu ya...:)
Bilgi paylaştıkça bir bakmışız; kar topu olmuş ve çığ gibi üzerimize geliyor...:)
Bu mesaj 2 defa değişti; son değiştiren: Salih Dinçer; zaman: 2012-09-16, 03:53.
acehreli (Moderatör) #3
Kullanıcı başlığı: Ali Çehreli
Üye Haz 2009 tarihinden beri · 4527 mesaj
Grup üyelikleri: Genel Moderatörler, Üyeler
Profili göster · Bu konuya bağlantı
Salih Dinçer:
Peki Kesir(4, 2) ile Kesir(5, 3)'ü toplamak için payda eşitleme ve sadeleştirme algoritmasından mı geçireceğiz?

Soruda 1/3 ile 20/60'ın aynı olmalarının beklendiği söylendiği için sadeleştirme şart değil.

Ama bu konuya çözüm sayfasında değiniyorum. Yoksa hemen hemen her işlem payın ve paydanın gittikçe büyümesine neden oluyor.

Ayrıca paydaları eşit olan (örneğin 1/3 + 2/3) olan sayıyı, paydaları eşit olduğu için önce payları toplayıp paydaları dokunmamak mümkün

İlk yaptığımda ben de öyle yapmıştım ama sonra başka bazı işlemlerde öyle bir şans olmadığı için ve toplamada bile nasıl olsa sadeleştirme gerektiğinden kod daha temiz kalsın diye onu da sonraya bıraktım.

Ali
acehreli (Moderatör) #4
Kullanıcı başlığı: Ali Çehreli
Üye Haz 2009 tarihinden beri · 4527 mesaj
Grup üyelikleri: Genel Moderatörler, Üyeler
Profili göster · Bu konuya bağlantı
Richard Harris ACCU'nun Overload dergisinde sayısal hesaplarla ilgili bir dizi makale yazmıştı. (Belki devam ediyordur. (?)) Bu yazılar hep "neden falanca yöntem kesirli sayı (floating point) dertlerinize çare değildir" gibi başlıklar taşır.

Bunlardan üçüncüsü kesirlilerin neden bir çözüm olmadığı üzerineydi. Derginin aşağıdaki sayısında "Why Rationals Won’t Cure Your Floating Point Blues" başlığı altında:

  http://accu.org/index.php/journals/c293/

Kesirli sayı türlerinin sorunu, irrasyonel değerleri tutamamalarıdır.

Ali
Avatar
Salih Dinçer #5
Üye Ock 2012 tarihinden beri · 1912 mesaj · Konum: İstanbul
Grup üyelikleri: Üyeler
Profili göster · Bu konuya bağlantı
Aşkın sayıları kast ediyor olmalısın. Değil mi hocam? Örneğin pi sayısı, e sayısı...

Aslında pi sayısı için kesirli sayılardan oluşan toplamlar/çarpımlar var. Örneğin ilk aklıma gelen çiflerin/teklere bölümleri:
(2/1 * 2/3 * 4/3 * 4/5 * 6/5 * 6/7 * 8/7 * 8/9 * 10/9)/2

Sanırım algoritması şöyle olabilir:
import std.stdio; ali.kesir;
void main() {
    Kesir xPi = new Kesir(2, 1);
    long i, pay, payda;
    for(i = 2; i < 1000; i++) {
        if (i % 2 == 0) pay = i; else payda = i;
        xPi *= Kesir(pay, payda);
    }
    xPi.writeln("/2", xPi/Kesir(2, 1));
} /*** DENENMEDİ ***/
Bilgi paylaştıkça bir bakmışız; kar topu olmuş ve çığ gibi üzerimize geliyor...:)
Avatar
Salih Dinçer #6
Üye Ock 2012 tarihinden beri · 1912 mesaj · Konum: İstanbul
Grup üyelikleri: Üyeler
Profili göster · Bu konuya bağlantı
Denedim, çok büyük sayılar çıkıyor; öyle ki long değerini aşıyor!

Bir kaç düzeltme ile kod şöyle:
import std.stdio, ali.kesir;
 
void main() {
    long i, pay = 2, payda = 1;
    auto xPi = Kesir(pay, payda);
 
    for(i = 2; i < 21; i++) {
        if(i % 2) payda = i;
        else pay= i;
        xPi *= Kesir(pay, payda);
        writef("%d/%d * ", pay,
                           payda);
    }
    writeln(xPi, " = ", cast(double)xPi);
} /* 1380784741023744000 /
   * 428670161650355625 = 3.22109...
   */
Çalışması için Ali hocamın şuradaki çözümünü üst satırına module ali.kesir; ekleyip alt satırında main () {} gizleyin...
Bilgi paylaştıkça bir bakmışız; kar topu olmuş ve çığ gibi üzerimize geliyor...:)
Bu mesaj Salih Dinçer tarafından değiştirildi; zaman: 2012-09-17, 11:57.
acehreli (Moderatör) #7
Kullanıcı başlığı: Ali Çehreli
Üye Haz 2009 tarihinden beri · 4527 mesaj
Grup üyelikleri: Genel Moderatörler, Üyeler
Profili göster · Bu konuya bağlantı
Yanıtlanan mesaj #5
Salih Dinçer:
Aşkın sayıları kast ediyor olmalısın.

Hiç duymamıştım, baktım; hayır, irrasyonel sayılardan bahsediyorum. Örneğin ikinin kökü irrasyonelmiş ama aşkın değilmiş.

pi sayısı için kesirli sayılardan oluşan toplamlar/çarpımlar var

Evet ama onlar sonsuz dizidir. Durulan noktaya bağlı olarak piye yaklaşılır ama hiçbir zaman ona eşit bir değer elde edilemez.

Ali
Doğrulama Kodu: VeriCode Lütfen resimde gördüğünüz doğrulama kodunu girin:
İfadeler: :-) ;-) :-D :-p :blush: :cool: :rolleyes: :huh: :-/ <_< :-( :'( :#: :scared: 8-( :nuts: :-O
Özel Karakterler:
Forum: Ders Arası RSS
Bağlı değilsiniz. · Şifremi unuttum · ÜYELİK
This board is powered by the Unclassified NewsBoard software, 20100516-dev, © 2003-10 by Yves Goergen
Şu an: 2017-11-19, 08:14:13 (UTC -08:00)