İngilizce Kaynaklar


Diğer




Karmaşık Sayı Türleri ve C++'nın std::complex'i

Not: Bu bir D1 karşılaştırmasıdır. Modern D'nin başka farklılıkları da var.

Bu sayfadaki bilgiler digitalmars.com sitesindeki eski bir yazıdan alınmıştır

Yazım güzelliği

C++'ta karmaşık sayı türleri şunlardır:

complex<float>
complex<double>
complex<long double>

C++'da sanal [imaginary] türler yoktur. D'de 3 karmaşık türe ek olarak 3 tane de sanal tür vardır:

cfloat
cdouble
creal
ifloat
idouble
ireal

C++'nın karmaşık sayıları aritmetik sabitlerle etkileşebilirler, ama sanal değer kavramı olmadığı için sanal değerler eklemek için kurucu fonksiyon kullanmak gerekir:

complex<long double> a = 5;                // a = 5 + 0i
complex<long double> b(0,7);               // b = 0 + 7i
c = a + b + complex<long double>(0,7);     // c = 5 + 14i

D'de sanal değer sabitlerinin sonuna i karakteri eklenir. Yukarıdaki kodun eşdeğeri şudur:

creal a = 5;                  // a = 5 + 0i
ireal b = 7i;                 // b = 7i
c = a + b + 7i;               // c = 5 + 14i

İçinde sabitlerin de olduğu daha uzun bir ifade:

c = (6 + 2i - 1 + 3i) / 3i;

C++'da şöyle yazılır:

c = (complex<double>(6,2) + complex<double>(-1,3))
                                        / complex<double>(0,3);

veya C++'ya da bir sanal tür eklenmiş olsaydı şöyle:

c = (6 + imaginary<double>(2) - 1 + imaginary<double>(3))
                                        / imaginary<double>(3);

Kısacası, D'de nn gibi bir sanal değer nni olarak yazılabilir; complex<long double>(0,nn) şeklinde nesne oluşturmaya gerek yoktur.

İşlemlerin etkinliği

C++'da sanal sayı türünün olmaması, 0 gerçel sayısının da işlemlerde gereksizce kullanılmasına neden olur. Örneğin D'de iki sanal değerin toplamı tek bir toplama işleminden oluşur:

ireal a, b, c;
c = a + b;

C++'da ise gerçel kısımlarının da toplandığı iki toplama işlemidir:

c.re = a.re + b.re;
c.im = a.im + b.im;

Durum çarpma işleminde daha da kötüdür: tek bir çarpma işlemi yerine 4 çarpma ve 2 toplama işlemi:

c.re = a.re * b.re - a.im * b.im;
c.im = a.im * b.re + a.re * b.im;

En kötüsü de bölme işlemidir: D'deki tek bir bölme işlemi yerine, C++'da normal bir gerçekleştirmede 1 karşılaştırma, 3 bölme, 3 çarpma, ve 3 toplama işlemi vardır:

if (fabs(b.re) < fabs(b.im))
{
    r = b.re / b.im;
    den = b.im + r * b.re;
    c.re = (a.re * r + a.im) / den;
    c.im = (a.im * r - a.re) / den;
}
else
{
    r = b.im / b.re;
    den = b.re + r * b.im;
    c.re = (a.re + r * a.im) / den;
    c.im = (a.im - r * a.re) / den;
}

Bu yavaşlıkları önlemek için sanal değer yerine C++'da double kullanılabilir. Örneğin D'deki şu kod:

cdouble c;
idouble im;
c *= im;

sanal kısım için double kullanılarak C++'da şöyle yazılabilir:

complex<double> c;
double im;
c = complex<double>(-c.imag() * im, c.real() * im);

Ama onun sonucunda da aritmetik işlemlerle kullanılabilen bir kütüphane türü kolaylığını artık geride bırakmış oluruz.

Anlam

Bütün bunların en kötüsü, sanal değer türünün eksikliği nedeniyle istemeden yanlış sonuçlar elde edilebilmesidir. Prof. Kahan'dan bir alıntı:

Fortran'da ve C/C++ derleyicileriyle gelen kütüphanelerde gereken karmaşık sayı fonksiyonları SQRT ve LOG, eğer IEEE 754 aritmetik işlemlerinde 0.0'ın işaretini gözardı edecek şekilde gerçekleştirilirlerse, karmaşık sayı z'nin negatif gerçel değer aldığı durumlarda artık

SQRT( CONJ( Z ) ) = CONJ( SQRT( Z ) )

ve

LOG( CONJ( Z ) ) = CONJ( LOG( Z ) )

eşitlikleri sağlanamaz. Karmaşık sayı aritmetiğinde işlemlerin gerçel ve sanal sayıların x + i*y şeklindeki soyutlamaları yerine (x, y) gibi çiftler üzerinde yapıldıkları durumlarda bu tür bozuklukların önüne geçmek imkansızdır. Karmaşık sayı işlemlerinde çiftlerin kullanılması yanlıştır; bir sanal türe ihtiyaç vardır.

Buradaki anlamsal sorunlar şunlardır:

C99 standardının Appendix G bölümü bu sorunla ilgili olarak bazı tavsiyelerde bulunur. Ancak o tavsiyeler C++98 standardına dahil olmadıklarından taşınabilirliklerine güvenilemez.

Kaynaklar