mirror of
https://github.com/dlang/phobos.git
synced 2025-05-01 23:50:31 +03:00
200 lines
5.2 KiB
D
200 lines
5.2 KiB
D
Ddoc
|
|
|
|
$(COMMUNITY D Complex Types and C++ std::complex,
|
|
|
|
How do D's complex numbers compare with C++'s std::complex class?
|
|
|
|
<h3>Syntactical Aesthetics</h3>
|
|
|
|
In C++, the complex types are:
|
|
|
|
$(CCODE
|
|
complex<float>
|
|
complex<double>
|
|
complex<long double>
|
|
)
|
|
|
|
C++ has no distinct imaginary type. D has 3 complex types and 3
|
|
imaginary types:
|
|
|
|
------------
|
|
cfloat
|
|
cdouble
|
|
creal
|
|
ifloat
|
|
idouble
|
|
ireal
|
|
------------
|
|
|
|
A C++ complex number can interact with an arithmetic literal, but
|
|
since there is no imaginary type, imaginary numbers can only be
|
|
created with the constructor syntax:
|
|
|
|
$(CCODE
|
|
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
|
|
)
|
|
|
|
In D, an imaginary numeric literal has the 'i' suffix.
|
|
The corresponding code would be the more natural:
|
|
|
|
------------
|
|
creal a = 5; // a = 5 + 0i
|
|
ireal b = 7i; // b = 7i
|
|
c = a + b + 7i; // c = 5 + 14i
|
|
------------
|
|
|
|
For more involved expressions involving constants:
|
|
|
|
------------
|
|
c = (6 + 2i - 1 + 3i) / 3i;
|
|
------------
|
|
|
|
In C++, this would be:
|
|
|
|
$(CCODE
|
|
c = (complex<double>(6,2) + complex<double>(-1,3)) / complex<double>(0,3);
|
|
)
|
|
|
|
or if an imaginary class were added to C++ it might be:
|
|
|
|
$(CCODE
|
|
c = (6 + imaginary<double>(2) - 1 + imaginary<double>(3)) / imaginary<double>(3);
|
|
)
|
|
|
|
In other words, an imaginary number $(I nn) can be represented with
|
|
just $(I nn)i rather than writing a constructor call
|
|
complex<long double>(0,$(I nn)).
|
|
|
|
<h3>Efficiency</h3>
|
|
|
|
The lack of an imaginary type in C++ means that operations on
|
|
imaginary numbers wind up with a lot of extra computations done
|
|
on the 0 real part. For example, adding two imaginary numbers
|
|
in D is one add:
|
|
|
|
------------
|
|
ireal a, b, c;
|
|
c = a + b;
|
|
------------
|
|
|
|
In C++, it is two adds, as the real parts get added too:
|
|
|
|
$(CCODE
|
|
c.re = a.re + b.re;
|
|
c.im = a.im + b.im;
|
|
)
|
|
|
|
Multiply is worse, as 4 multiplies and two adds are done instead of
|
|
one multiply:
|
|
|
|
$(CCODE
|
|
c.re = a.re * b.re - a.im * b.im;
|
|
c.im = a.im * b.re + a.re * b.im;
|
|
)
|
|
|
|
Divide is the worst - D has one divide, whereas C++ implements
|
|
complex division with typically one comparison, 3 divides,
|
|
3 multiplies and 3 additions:
|
|
|
|
$(CCODE
|
|
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;
|
|
}
|
|
)
|
|
|
|
To avoid these efficiency concerns in C++, one could simulate
|
|
an imaginary number using a double. For example, given the D:
|
|
|
|
------------
|
|
cdouble c;
|
|
idouble im;
|
|
c *= im;
|
|
------------
|
|
|
|
it could be written in C++ as:
|
|
|
|
$(CCODE
|
|
complex<double> c;
|
|
double im;
|
|
c = complex<double>(-c.imag() * im, c.real() * im);
|
|
)
|
|
|
|
but then the advantages of complex being a library type integrated
|
|
in with the arithmetic operators have been lost.
|
|
|
|
<h3>Semantics</h3>
|
|
|
|
Worst of all, the lack of an imaginary type can cause the wrong
|
|
answer to be inadvertently produced.
|
|
To quote <a href="http://www.cs.berkeley.edu/~wkahan/">
|
|
Prof. Kahan</a>:
|
|
|
|
$(BLOCKQUOTE
|
|
"A streamline goes astray when the complex functions SQRT and LOG
|
|
are implemented, as is necessary in Fortran and in libraries
|
|
currently distributed with C/C++ compilers, in a way that
|
|
disregards the sign of 0.0 in IEEE 754 arithmetic and consequently
|
|
violates identities like SQRT( CONJ( Z ) ) = CONJ( SQRT( Z ) ) and
|
|
LOG( CONJ( Z ) ) = CONJ( LOG( Z ) ) whenever the COMPLEX variable Z
|
|
takes negative real values. Such anomalies are unavoidable if
|
|
Complex Arithmetic operates on pairs (x, y) instead of notional
|
|
sums x + i*y of real and imaginary
|
|
variables. The language of pairs is $(I incorrect) for Complex
|
|
Arithmetic; it needs the Imaginary type."
|
|
)
|
|
|
|
The semantic problems are:
|
|
|
|
$(UL
|
|
$(LI Consider the formula (1 - infinity*$(I i)) * $(I i) which
|
|
should produce (infinity + $(I i)). However, if instead the second
|
|
factor is (0 + $(I i)) rather than just $(I i), the result is
|
|
(infinity + NaN*$(I i)), a spurious NaN was generated.
|
|
)
|
|
|
|
$(LI A distinct imaginary type preserves the sign of 0, necessary
|
|
for calculations involving branch cuts.
|
|
)
|
|
)
|
|
|
|
Appendix G of the C99 standard has recommendations for dealing
|
|
with this problem. However, those recommendations are not part
|
|
of the C++98 standard, and so cannot be portably relied upon.
|
|
|
|
<h3>References</h3>
|
|
|
|
<a href="http://www.cs.berkeley.edu/~wkahan/JAVAhurt.pdf">
|
|
How Java's Floating-Point Hurts Everyone Everywhere</a>
|
|
Prof. W. Kahan and Joseph D. Darcy
|
|
<p>
|
|
|
|
<a href="http://www.cs.berkeley.edu/~wkahan/Curmudge.pdf">
|
|
The Numerical Analyst as Computer Science Curmudgeon</a>
|
|
by Prof. W. Kahan
|
|
<p>
|
|
|
|
"Branch Cuts for Complex Elementary Functions,
|
|
or Much Ado About Nothing's Sign Bit"
|
|
by W. Kahan, ch.<br>
|
|
7 in The State of the Art in Numerical Analysis (1987)
|
|
ed. by M. Powell and A. Iserles for Oxford U.P.
|
|
|
|
)
|
|
|
|
Macros:
|
|
TITLE=D Complex Types vs C++ std::complex
|
|
WIKI=CPPcomplex
|
|
|