|Not so complex numbers in C#|
|Written by Mike James|
|Tuesday, 25 March 2014|
Page 1 of 2
Did you know that .NET supports complex numbers? The Complex struct is easy to use and just needs a little extra publicity so that we all remember it's there!
Not so complex numbers
Support for complex numbers was introduced in C# 4.0 but it's been easy to miss this revolution in C# number crunching.
Of course complex numbers don't matter to everyone - in fact you might say that they are a minority interest but they deserve support in any programming language. It is often said that the only real reason scientists still use Fortran is that it has out-of-the-box support for complex numbers where other languages have to retrofit using a package of some kind.
The new features are contained in System.Numerics but this isn't included automatically in a startup project - yes complex numbers are always going to be a niche topic.
To make use of it you have to right click on the References in the Solution Explorer and add System.Numerics. To make using the new features easier also add:
To get started let's create the "hello world" of any numeric program and add two numbers together:
If you can't see the Output Window make sure you have selected it using Debug,Windows.You should see the result (0,1).
The points to note are - you can create and initialise a complex number using the constructor, the + operator is overloaded to perform complex addition and return a new Complex object as its result and the ToString method returns the number in Cartesian form.
There are other ways to create a Complex object, however.
For example you can assign a real number to create a Complex number with imaginary component set to zero. For example:
is the same as:
There are implicit and explicit conversion operators for most of the reasonable and common data types. So if it makes sense you can assign a real value to a complex variable.
Notice that the real and imaginary components of Complex are stored as Double. Also notice that the BigInteger struct can be cast to Complex.
However you can't directly assign complex or imaginary quantity. That is z2=(0,1) or any variation on this doesn't work.
The static field ImaginaryOne can be used to create a complex number set equal to i. That is:
is the same as
You can use the Real and Imaginary properties to read the real and imaginary components of a complex number but you cannot use them to set a value.
That is an instruction like:
is illegal. If you find this surprising you are not alone.
The reason that you can't use the Real and Imaginary properties to modify a Complex object is that it has been designed to be immutable - once created you cannot change it.
Again this too sounds like a surprising choice but immutability is something that is becoming increasingly favoured as a design concept in modern programming language design. To ensure that the Complex type is immutable all of its properties - Real, Imaginary, Magnitude and Phase - are read-only.
The next obvious question is if Complex is immutable how do you use it to do arithmetic where things must change? The answer is that each time assignment occurs a new instance of the Complex object is created.
For example when you write
the operation of addition creates a new Complex object which z3 is set equal to.That is:
is exactly equivalent to:
Contrast this with the more direct approach of storing the real and imaginary results of performing the addition into the Real and Imaginary properties of z3.
In the same way
calls the implicit conversion method which creates an instance of Complex which z2 is set to. That is:
is exactly equivalent to:
Complex is a struct not a class and this means it follows value semantics - which is what you generally want.
For example, when you pass a Complex type as a parameter it will be passed by value and any changes that you make to the parameter do not alter the original variable. If you want to pass the parameter by reference so that it can be changed simply add ref in front of the parameter.
As well as creating Complex objects using the Cartesian form you can also use the static FromPolarCoodinates method:
As well as complex addition you can use subtraction, multiplication, division and unary negation. The operators also work with mixed real and complex values.
For example you can write:
The inequality operator is also overloaded allowing you to compare two complex values.
Of course the greater than less than operators are not defined because the concept of greater than/less than makes no sense for complex numbers.
All of the overloaded operators are also available as static methods.
Notice that we can convert between polar and Cartesian form immediately because of the Magnitude and Phase properties. Of course these are read-only as already mentioned.
The fact that we have the usual arithmetic operations is all very well but to do anything useful with complex number we also need some higher operations and functions. In particular we need a power operator. It is one of the peculiarities of C# that it doesn't support a raise to the power operator.
Where other languages would write X^2 or X**2 C# uses Math.Pow(X,2), which to any mathematically trained person is barbaric.
There are arguments for why C# does it this way - but none justify the loss of a power operator.To make things worse the ^ operator is defined as XOR which makes it possible to translate a formula incorrectly but not realise what the problem is until much time has been wasted.
The Complex type simply adds its own version of Pow.
If you write Complex.Pow(Z,X) then it will compute Z^X where X is a double. So to square Z you would write
In most cases it would be better to write Z*Z or Z*Z*Z rather than use the Pow method. As these forms are much more efficient than a general power calculation which generally involves logs and inverse logs doing things this way is no bad thing.
There is also a doubly complex Pow method Pow(Z1,Z2) where both are complex and this computes Z1Z2. Don't use it if Z2 is in fact real because it's not very fast.
Once you have got over the shock of the way that you calculate a power the rest of the Complex functions look reasonable.
There is a conjugate method for example but no unary conjugate operator and a Reciprocal method that works out 1/z - hopefully faster and more accurately than doing the division. All of the complex trig and hyperbolic functions are defined. Inverse trig functions are provided but not inverse hyperbolic. Natural log, log to any base and log to base 10 are provided as is Exp. Other functions can generally be made up using these as the building blocks.
At this point what could be more appropriate than to program the most incredible formula in the mathematical world. As is well known (I hope)
Thus relating together the numbers e, i, Pi and -1.
In C# this becomes:
The result printed is:
which isn't quite (-1,0) and this should also give you some idea of the (in)accuracy involved in using complex Exp.
|Last Updated ( Tuesday, 25 March 2014 )|