Complex MultiplicationComplex numbers are now part of the C standard as of C99. They were added because complex numbers are extremely common in mathematics and engineering. This is the case because complex numbers close over the space of solutions of polynomials. Even though i is just the solution of x2+1=0, adding it to the real number system allows one to express the solutions of any other polynomial, including those involving expressions with complex coefficients. The addition to the C standard has enabled C to become much more useful in a numerical context. Simply by adding
However the above isn't the whole story. We can compile the code and see what is produced. The complex addition function is rather compact, being implemented as two normal floating point additions:
Complex multiplication is a different story, here the result isn't quite so optimized:
Instead of being implemented inline, the compiler calls a massive (700+ byte) function to implement the complex multiplication operation. Why? The obvious way to do the multiplication is as you would on pen and paper:
The problem with the above is that floating point numbers are not the same as the real number system. Doing the above as written will give incorrect results in some cases. Floating point numbers can represent several different types of thing. The first thing they obviously can be is a normal floating point number. The second are numbers with reduced precision, called "denormal numbers". The third are two different types of zero, both positive and negative. The fourth is two types of infinity, positive and negative. Finally, a floating point number could be a "NaN", meaning "Not a Number". A NaN can be signed or not, and "signalling" or not. NaN's are special. A signalling NaN will cause the current execution of the program to be halted, and control transferred to some sort of error handler. A non-signalling NaN will contaminate any other number it comes in contact with, as any arithmetic operation involving a NaN will produce a NaN as the answer. In addition, it is possible (but rarely used) to hide user information within a NaN in spare bits. These abilities allow NaN's to denote missing data and results of expressions which are either mathematically undefined, or inexpressible on the real axis. It is possible to generate a NaN with simple arithmetic. Some examples are Complex numbers are a little different. They have both real and imaginary parts which are floating point numbers, so any of the above possibilities can occur for each. The problem with this is that not all possibilities have the same mathematical meaning as you might expect. The first problem is with zero. Normal floating point numbers have a sign for zero, as zero can be positive or negative. The reasoning for this is that some functions have different limits as you approach zero from the positive or negative direction along the real axis due to branch-cuts. Another reason is that the inclusion of a negative zero gives every floating point number a multiplicative inverse. The inverse of positive infinity is zero, and the inverse of negative infinity is negative zero. This additional number improves the numerical behaviour of many formulae at their mathematical extremes. The problem with translating the negative zero concept to complex numbers is that an infinite number of them would be required. One can approach zero along the real axis in only two ways, but in the complex plane any angle is possible. Thus, even though negative zeros may exist in the real and imaginary parts making up a complex number it is not worth much effort in calculating them correctly as their mathematical meaning is greatly diminished. Similarly, there is a problem with infinity. In the real number case, there are two infinities, Thus only if a complex number has both components a NaN, is it really a "Not a Number". A number with only one NaN could be an infinity, or simply only partially defined. This poses a problem with naive complex multiplication, as the complex infinity containing a NaN would be wrongly converted into a complex NaN as the individual floating point multiplications and additions spread the NaN "contamination". Due to the difficulty of getting the "simple" act of complex multiplication correct, a function describing it explicitly is shown in Annex G.5.1.6:
The above is extremely similar to the code within libgcc called by gcc to implement the complex multiply operation (__muldc3). It firstly calculates the result of the multiplication in the naive way. If the result is a complex NaN, then it recalculates more carefully. If it isn't a complex Nan, with both real and imaginary parts set to NaN, then it assumes the answer is correct and returns it. Unfortunately, the above can lead to loss of precision in some cases where there is excess cancellation in the addition and subtraction within the imaginary and real parts respectively. However, removing the cases where that matters is quite difficult to do efficiently in software. The above usually only needs to make one (failed) check against NaN, which makes it quite fast. So how are all the special cases handled by the above? The main point of the algorithm is to try to "divide out" by infinity converting a complex infinity into a normal floating point pair. By doing the arithmetic with normal numbers the bad cases are not triggered. Finally the answer can be multiplied by infinity at the end, restoring the complex infinity state. If instead the result is zero, then The third if statement in the NaN handler is much more subtle. Note that the individual floating point multiplications that make up the complex multiply can overflow. Thus we need to worry about when an infinity is produced from those. Finally, it looks like it is possible to create an The third if statement actually only effects the case where you are multiplying with a number that is partially a NaN. i.e. the real or imaginary part is NaN, but not both. If the number you are multiplying by is not a complex infinity, then the result is undefined in this case - so the original answer of a complex NaN was correct. However, a partial NaN multiplied by a large number that overflows to infinity will produce a complex infinity. (Think about it, no matter what the value of the NaN, the result of the multiplication will have an overflowing norm.) If you do not use partial NaN's then perhaps this check could be removed... SummaryComplex multiplication is much more complicated than it first appears due to the existence of NaN's within the floating point specification. The C standard specifies how it may be implemented with rather little overhead over the naive method. However, some users may not want any overhead at all, and would like to ignore the special cases triggered by complex infinities and NaN's. Such users can use compiler flags such as "-ffast-math" on gcc to remove overhead on a per-source-file level. This causes everything to be inlined, and many more optimizations to be done. It may be interesting to compare this to the case of integers. There are several different integer types defined by the C standard. Some of the them, such as Hypothetically, the current |
About Us | Returns Policy | Privacy Policy | Send us Feedback |
Company Info |
Product Index |
Category Index |
Help |
Terms of Use
Copyright © Lockless Inc All Rights Reserved. |
Comments
Barak A. Pearlmutter said...(a+ib)(c+id) = ac - bd + i(bc + bd)
it should be
(a+ib)(c+id) = ac - bd + i(bc + ad)
i.e if z1=r1*exp(x1) and z2=r2*exp(x2) , then z1*z2=r1*r2*exp(x1+x2)