D
Linguagem
Phobos
Comparações

· D vs C/C++/C#/Java
· Razão para Construções
· Convertendo C para D
· Convertendo C++ para D
· O Pré-Processador de C vs D
· Strings em D vs std::string em C++
· Complexos em D vs std::complex em C++
· Programação de Contrato em D vs C++

Tipos Complexos em D e std::complex em C++

Como fazer números complexos em D se compararem com a classe std::complex em C++?

Estéticas Sintáticas

Em C++, os tipos complexos são:
complex<float>
complex<double>
complex<long double>
C++ não tem um tipo imaginário distinto. D tem 3 tipos complexos e 3 tipos imaginários:
cfloat
cdouble
creal
ifloat
idouble
ireal
Um número complexo em C++ pode interagir com um literal aritmético, mas já que não há tipo imaginário, números imaginários podem ser criados somente com a sintaxe de contrutor:
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
Em D, um literal numérico imaginário tem o sufixo 'i'. O código correspondente seria o mais natural:
creal a = 5; // a = 5 + 0i
ireal b = 7i; // b = 7i
c = a + b + 7i; // c = 5 + 14i
Para expressões mais envolvidas envolvendo constantes:
c = (6 + 2i - 1 + 3i) / 3i;
Em C++, isso seria:
c = (complex<double>(6,2) + complex<double>(-1,3)) / complex<double>(0,3);
ou se uma classe imaginária fosse acrescentada à C++ poderia ser:
c = (6 + imaginary<double>(2) - 1 + imaginary<double>(3)) / imaginary<double>(3);
Em outras palavras, um número imaginário nn pode ser representado com apenas nni ao invés de escrever uma chamada à construtor complex<long double>(0,nn).

Eficiência

A falta de um tipo imaginário em C++ significa que operações em números imaginários ventam com várias cumputações extras feitas na parte real 0. Por exemplo, somar dois números imaginários em D é uma adição:
ireal a, b, c;
c = a + b;
Em C++, são duas adições, como a parte real é somada também:
c.re = a.re + b.re;
c.im = a.im + b.im;
Multiplicar é pior, como 4 multiplicações e duas adições são feitas ao invés de uma multiplicação:
c.re = a.re * b.re - a.im * b.im;
c.im = a.im * b.re + a.re * b.im;
Dividir é o pior - D tem uma divisão, considerando que C++ implementa divisão de complexos com tipicamente uma comparação, 3 divisões, 3 multiplicações e 3 somas:
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;
}
Para evitar essas preocupações com eficiência em C++, alguém poderia simular um número imaginário usando um double. Por exemplo, dado em D:
cdouble c;
idouble im;
c *= im;
isso poderia ser escrito em C++ como:
complex<double> c;
double im;
c = complex<double>(-c.imag() * im, c.real() * im);
mas as vantagens de complexos serem um tipo de biblioteca integrada com os operadores aritméticos seria perdida.

Semântica

Pior de tudo, a falta de um tipo imaginário pode causar a resposta errada a ser inadivertidamente produzida. Para citar Prof. Kahan:
"Um fluxo vai desencaminhado quando as funções complexas SQRT e LOG são implementadas, como é necessário em Fortran e em bibliotecas atualmente distribuídas em compiladores C/C++, de forma que desconsideram o sinal de 0.0 em aritmética IEEE 754 e conseqüêntemente violam identidades como SQRT( CONJ( Z ) ) = CONJ( SQRT( Z ) ) e LOG( CONJ( Z ) ) = CONJ( LOG( Z ) ) sempre que a variável COMPLEXA Z tem valores reais negativos. Tais anomalias são inevitáveis se Aritmética de Complexos opera em pares (x, y) ao invés de somas nocionais x + i*y de variáveis imaginárias e reais. A linguagem de pares é incorreta para Aritmética Complexa; ela precisa de em Tipo Imaginário."
Os problemas de semântica são:
  • Considere a fórmula (1 - infinito*i) * i que deveria produzir (infinito + i). Porém, se o segundo fator for (0 + i) ao invés de apenas i, o resultado é (infinito + NaN*i), um espúrioso NaN foi gerado.
  • Um tipo imaginário distinto preserva o sinal de 0, necessário para calculos envolvendo cortes filiais.
Apêndice G do padrão C99 tem recomendações para lidar com esse problema. Porém, essas recomendações não são parte do padrão C++98, assim não podem ser confiávelmente portáveis.

Referências

How Java's Floating-Point Hurts Everyone Everywhere Prof. W. Kahan and Joseph D. Darcy

The Numerical Analyst as Computer Science Curmudgeon by Prof. W. Kahan

"Branch Cuts for Complex Elementary Functions, or Much Ado About Nothing's Sign Bit" by W. Kahan, ch.
7 in The State of the Art in Numerical Analysis (1987) ed. by M. Powell and A. Iserles for Oxford U.P.