| 1 |
Ddoc |
|---|
| 2 |
|
|---|
| 3 |
$(COMMUNITY D Complex Types and C++ std::complex, |
|---|
| 4 |
|
|---|
| 5 |
How do D's complex numbers compare with C++'s std::complex class? |
|---|
| 6 |
|
|---|
| 7 |
<h3>Syntactical Aesthetics</h3> |
|---|
| 8 |
|
|---|
| 9 |
In C++, the complex types are: |
|---|
| 10 |
|
|---|
| 11 |
$(CCODE |
|---|
| 12 |
complex<float> |
|---|
| 13 |
complex<double> |
|---|
| 14 |
complex<long double> |
|---|
| 15 |
) |
|---|
| 16 |
|
|---|
| 17 |
C++ has no distinct imaginary type. D has 3 complex types and 3 |
|---|
| 18 |
imaginary types: |
|---|
| 19 |
|
|---|
| 20 |
------------ |
|---|
| 21 |
cfloat |
|---|
| 22 |
cdouble |
|---|
| 23 |
creal |
|---|
| 24 |
ifloat |
|---|
| 25 |
idouble |
|---|
| 26 |
ireal |
|---|
| 27 |
------------ |
|---|
| 28 |
|
|---|
| 29 |
A C++ complex number can interact with an arithmetic literal, but |
|---|
| 30 |
since there is no imaginary type, imaginary numbers can only be |
|---|
| 31 |
created with the constructor syntax: |
|---|
| 32 |
|
|---|
| 33 |
$(CCODE |
|---|
| 34 |
complex<long double> a = 5; // a = 5 + 0i |
|---|
| 35 |
complex<long double> b(0,7); // b = 0 + 7i |
|---|
| 36 |
c = a + b + complex<long double>(0,7); // c = 5 + 14i |
|---|
| 37 |
) |
|---|
| 38 |
|
|---|
| 39 |
In D, an imaginary numeric literal has the $(SINGLEQUOTE i) suffix. |
|---|
| 40 |
The corresponding code would be the more natural: |
|---|
| 41 |
|
|---|
| 42 |
------------ |
|---|
| 43 |
creal a = 5; // a = 5 + 0i |
|---|
| 44 |
ireal b = 7i; // b = 7i |
|---|
| 45 |
c = a + b + 7i; // c = 5 + 14i |
|---|
| 46 |
------------ |
|---|
| 47 |
|
|---|
| 48 |
For more involved expressions involving constants: |
|---|
| 49 |
|
|---|
| 50 |
------------ |
|---|
| 51 |
c = (6 + 2i - 1 + 3i) / 3i; |
|---|
| 52 |
------------ |
|---|
| 53 |
|
|---|
| 54 |
In C++, this would be: |
|---|
| 55 |
|
|---|
| 56 |
$(CCODE |
|---|
| 57 |
c = (complex<double>(6,2) + complex<double>(-1,3)) / complex<double>(0,3); |
|---|
| 58 |
) |
|---|
| 59 |
|
|---|
| 60 |
or if an imaginary class were added to C++ it might be: |
|---|
| 61 |
|
|---|
| 62 |
$(CCODE |
|---|
| 63 |
c = (6 + imaginary<double>(2) - 1 + imaginary<double>(3)) / imaginary<double>(3); |
|---|
| 64 |
) |
|---|
| 65 |
|
|---|
| 66 |
In other words, an imaginary number $(I nn) can be represented with |
|---|
| 67 |
just $(I nn)i rather than writing a constructor call |
|---|
| 68 |
complex<long double>(0,$(I nn)). |
|---|
| 69 |
|
|---|
| 70 |
<h3>Efficiency</h3> |
|---|
| 71 |
|
|---|
| 72 |
The lack of an imaginary type in C++ means that operations on |
|---|
| 73 |
imaginary numbers wind up with a lot of extra computations done |
|---|
| 74 |
on the 0 real part. For example, adding two imaginary numbers |
|---|
| 75 |
in D is one add: |
|---|
| 76 |
|
|---|
| 77 |
------------ |
|---|
| 78 |
ireal a, b, c; |
|---|
| 79 |
c = a + b; |
|---|
| 80 |
------------ |
|---|
| 81 |
|
|---|
| 82 |
In C++, it is two adds, as the real parts get added too: |
|---|
| 83 |
|
|---|
| 84 |
$(CCODE |
|---|
| 85 |
c.re = a.re + b.re; |
|---|
| 86 |
c.im = a.im + b.im; |
|---|
| 87 |
) |
|---|
| 88 |
|
|---|
| 89 |
Multiply is worse, as 4 multiplies and two adds are done instead of |
|---|
| 90 |
one multiply: |
|---|
| 91 |
|
|---|
| 92 |
$(CCODE |
|---|
| 93 |
c.re = a.re * b.re - a.im * b.im; |
|---|
| 94 |
c.im = a.im * b.re + a.re * b.im; |
|---|
| 95 |
) |
|---|
| 96 |
|
|---|
| 97 |
Divide is the worst - D has one divide, whereas C++ implements |
|---|
| 98 |
complex division with typically one comparison, 3 divides, |
|---|
| 99 |
3 multiplies and 3 additions: |
|---|
| 100 |
|
|---|
| 101 |
$(CCODE |
|---|
| 102 |
if (fabs(b.re) < fabs(b.im)) |
|---|
| 103 |
{ |
|---|
| 104 |
r = b.re / b.im; |
|---|
| 105 |
den = b.im + r * b.re; |
|---|
| 106 |
c.re = (a.re * r + a.im) / den; |
|---|
| 107 |
c.im = (a.im * r - a.re) / den; |
|---|
| 108 |
} |
|---|
| 109 |
else |
|---|
| 110 |
{ |
|---|
| 111 |
r = b.im / b.re; |
|---|
| 112 |
den = b.re + r * b.im; |
|---|
| 113 |
c.re = (a.re + r * a.im) / den; |
|---|
| 114 |
c.im = (a.im - r * a.re) / den; |
|---|
| 115 |
} |
|---|
| 116 |
) |
|---|
| 117 |
|
|---|
| 118 |
To avoid these efficiency concerns in C++, one could simulate |
|---|
| 119 |
an imaginary number using a double. For example, given the D: |
|---|
| 120 |
|
|---|
| 121 |
------------ |
|---|
| 122 |
cdouble c; |
|---|
| 123 |
idouble im; |
|---|
| 124 |
c *= im; |
|---|
| 125 |
------------ |
|---|
| 126 |
|
|---|
| 127 |
it could be written in C++ as: |
|---|
| 128 |
|
|---|
| 129 |
$(CCODE |
|---|
| 130 |
complex<double> c; |
|---|
| 131 |
double im; |
|---|
| 132 |
c = complex<double>(-c.imag() * im, c.real() * im); |
|---|
| 133 |
) |
|---|
| 134 |
|
|---|
| 135 |
but then the advantages of complex being a library type integrated |
|---|
| 136 |
in with the arithmetic operators have been lost. |
|---|
| 137 |
|
|---|
| 138 |
<h3>Semantics</h3> |
|---|
| 139 |
|
|---|
| 140 |
Worst of all, the lack of an imaginary type can cause the wrong |
|---|
| 141 |
answer to be inadvertently produced. |
|---|
| 142 |
To quote <a href="http://www.cs.berkeley.edu/~wkahan/"> |
|---|
| 143 |
Prof. Kahan</a>: |
|---|
| 144 |
|
|---|
| 145 |
$(BLOCKQUOTE |
|---|
| 146 |
"A streamline goes astray when the complex functions SQRT and LOG |
|---|
| 147 |
are implemented, as is necessary in Fortran and in libraries |
|---|
| 148 |
currently distributed with C/C++ compilers, in a way that |
|---|
| 149 |
disregards the sign of 0.0 in IEEE 754 arithmetic and consequently |
|---|
| 150 |
violates identities like SQRT( CONJ( Z ) ) = CONJ( SQRT( Z ) ) and |
|---|
| 151 |
LOG( CONJ( Z ) ) = CONJ( LOG( Z ) ) whenever the COMPLEX variable Z |
|---|
| 152 |
takes negative real values. Such anomalies are unavoidable if |
|---|
| 153 |
Complex Arithmetic operates on pairs (x, y) instead of notional |
|---|
| 154 |
sums x + i*y of real and imaginary |
|---|
| 155 |
variables. The language of pairs is $(I incorrect) for Complex |
|---|
| 156 |
Arithmetic; it needs the Imaginary type." |
|---|
| 157 |
) |
|---|
| 158 |
|
|---|
| 159 |
The semantic problems are: |
|---|
| 160 |
|
|---|
| 161 |
$(UL |
|---|
| 162 |
$(LI Consider the formula (1 - infinity*$(I i)) * $(I i) which |
|---|
| 163 |
should produce (infinity + $(I i)). However, if instead the second |
|---|
| 164 |
factor is (0 + $(I i)) rather than just $(I i), the result is |
|---|
| 165 |
(infinity + NaN*$(I i)), a spurious NaN was generated. |
|---|
| 166 |
) |
|---|
| 167 |
|
|---|
| 168 |
$(LI A distinct imaginary type preserves the sign of 0, necessary |
|---|
| 169 |
for calculations involving branch cuts. |
|---|
| 170 |
) |
|---|
| 171 |
) |
|---|
| 172 |
|
|---|
| 173 |
Appendix G of the C99 standard has recommendations for dealing |
|---|
| 174 |
with this problem. However, those recommendations are not part |
|---|
| 175 |
of the C++98 standard, and so cannot be portably relied upon. |
|---|
| 176 |
|
|---|
| 177 |
<h3>References</h3> |
|---|
| 178 |
|
|---|
| 179 |
<a href="http://www.cs.berkeley.edu/~wkahan/JAVAhurt.pdf"> |
|---|
| 180 |
How Java's Floating-Point Hurts Everyone Everywhere</a> |
|---|
| 181 |
Prof. W. Kahan and Joseph D. Darcy |
|---|
| 182 |
<p> |
|---|
| 183 |
|
|---|
| 184 |
<a href="http://www.cs.berkeley.edu/~wkahan/Curmudge.pdf"> |
|---|
| 185 |
The Numerical Analyst as Computer Science Curmudgeon</a> |
|---|
| 186 |
by Prof. W. Kahan |
|---|
| 187 |
<p> |
|---|
| 188 |
|
|---|
| 189 |
$(DOUBLEQUOTE Branch Cuts for Complex Elementary Functions, |
|---|
| 190 |
or Much Ado About Nothing's Sign Bit) |
|---|
| 191 |
by W. Kahan, ch.<br> |
|---|
| 192 |
7 in The State of the Art in Numerical Analysis (1987) |
|---|
| 193 |
ed. by M. Powell and A. Iserles for Oxford U.P. |
|---|
| 194 |
|
|---|
| 195 |
) |
|---|
| 196 |
|
|---|
| 197 |
Macros: |
|---|
| 198 |
TITLE=D Complex Types vs C++ std::complex |
|---|
| 199 |
WIKI=CPPcomplex |
|---|