Home  Introduction  Complex numbers  Newton's method  Newton's method with complex numbers  Fractals  Optimisations  Performance

Search this site:
Threads Database Profiling Regular expressions Random numbers Compression Exceptions C Equivalents in Java
 Got a question about Java? Java discussion forum

The cost of Java object orientation in mathematical calculations: introducing a ComplexNumber class

On the previous page, we introduced Newton's method and showed an example method to find the real solution to an equation:

public static void solveEquation() {
  double x = 100d;
  for (int i = 0; i < 20; i++) {
    double num = x*x*x + x*x - 7;
    double den = 3*x*x + 2*x;
    double diff = num / den;
    if (diff == 0) break;
    x -= diff;
    System.out.println(x);
  }
}

What we'd now like to do is transform this method into a version that uses complex numbers. We will implement a ComplexNumber class to encapsulate a complex number, so that we can refactor the previous method to use this class. The new version would look something like the following:

public static void solveEquationComplex() {
  ComplexNumber x = new ComplexNumber(0d, 100d);
  final ComplexNumber seven = new ComplexNumber(7d, 0);
  final ComplexNumber three = new ComplexNumber(3d, 0);
  final ComplexNumber two = new ComplexNumber(2d, 0);
		
  for (int i = 0; i < 20; i++) {
    ComplexNumber xsquared = x.multiply(x);
    ComplexNumber xcubed = xsquared.multiply(x);
    ComplexNumber xdoubled = x.multiply(two);
			
    ComplexNumber num = xcubed.add(xsquared).subtract(seven);
    ComplexNumber den = xsquared.multiply(three).add(xdoubled);
    ComplexNumber diff = num.divide(den);
    if (diff.real() == 0 && diff.imaginary() == 0) {
      break;
    }
    x = x.subtract(diff);
    System.out.println(x);
  }
}

As before, we'll live with the comparison with zero for now, although in reality it will actually introduce some slight artefacts into the image.

Notice how when we encapsulate numbers as objects, we have to "spell things out" a lot more. We can't rely on the compiler to implement order precedence, for example, so we have to manually make sure that we calculate the multiplicatuions before the divisions.

A design decision that we have to make is whether to make an instance of ComplexNumber immutable. Making instances immutable means that all its fields are fianl and once a ComplexNumber is created, its value cannot be changed, and the only way to represent a modification to the number is to create a new object. Hence in this implementation, methods such as add(), multiply() etc actually return new object representing the result. A partial implementation would look something like this:

public class ComplexNumber {
  private final double real, imag;
	
  public ComplexNumber(double real, double imag) {
    this.real = real;
    this.imag = imag;
  }
  public ComplexNumber add(ComplexNumber other) {
    return new ComplexNumber(this.real + other.real,
                             this.imag + other.imag);
  }
  public ComplexNumber multiply(ComplexNumber other) {
    double a = this.real;   double b = this.imag;
    double c = other.real;  double d = other.imag;
    return new ComplexNumber(a * c - b * d, b * c + a * d);
  }	
  public ComplexNumber divide(ComplexNumber other) {
    double a = this.real;   double b = this.imag;
    double c = other.real;  double d = other.imag;
    double denom = c * c + d * d;
    return new ComplexNumber((a * c + b * d) / denom,
                             (b * c - a * d) / denom);
  }
  ...
}

The implementations of the various methods essentially apply methodically the rules for operations on complex numbers that fall out of standard algebra, as discussed in our introduction to complex numbers. From the above, implementing other methods such as subtract(), real(), imaginary(), toString() should be straightforward. You could also envisage adding methods such as addReal(), multiplyReal() which just take a double constant, to avoid the clumsiness of having to define objects such as two, three in the code above.

Immutability

Notice how the components of the complex number, real and imag, are immutable (declared final). Why make instances mutable when we know that we'll end up having to create more objects this way and, as your gran taught you, "creating a lot of objects is bad for performance"? Well, at least the following reasons:

  • having the class immutable is often better from the point of view of simplicity of implementation and maintainability: if ComplexNumber was immutable, it would be easy to introduce bugs by accidentally modifying a given instance;
  • immutable classes are automatically thread-safe, and if we're performing some processor-intensive number-crunching, we may well want to take advantage of a multi-processor system;
  • it turns out that performance-wise, creating lots of simple objects at least is not actually all that bad.

Example output

With an implementation such as the above, the output of solveEquationComplex() looks as follows:

-0.11133949602239901+66.66592440335985i
-0.18585316787849726+44.44283505958452i
-0.23617642341226036+29.62688233513716i
-0.2711836216227622+19.748737755796576i
-0.2978059397689552+13.162038215112432i
-0.32295073207035885+8.768996085681543i
-0.3563813852246698+5.837491430549413i
-0.41627417636444985+3.879544518659998i
-0.5410864846475445+2.5738748568152214i
-0.8103885985374859+1.7415280579523365i
-1.2588571972821643+1.46289419770466i
-1.3187032281000435+1.6128030620364568i
-1.3155473169483967+1.6003720738120604i
-1.3155496466181746+1.6002853958537022i
-1.3155496487694949+1.6002853925452278i
-1.3155496487694949+1.6002853925452278i
-1.3155496487694949+1.6002853925452278i
-1.3155496487694949+1.6002853925452278i
-1.3155496487694949+1.6002853925452278i
-1.3155496487694949+1.6002853925452278i

Try initialising x with a negative complex number and with a real number (i.e. setting the second parameter to the cosntructor to zero) and you should get the other two solutions:

-1.3155496487694949-1.6002853925452278i
1.6310992975389897+0.0i

Next: performance in calculation-intensive situations

In this particular case, we are performing so few calculations that the efficiency of our implementation is in reality not an issue and opting for the more maintainable solution is a no-brainer. But what if the volume of complex number calculations that we need to perform is so great that the choice of implementation does make a tangible performance difference? On the next page, we return to this matter with the example of creating fractals with Newton's method.


Unless otherwise stated, the Java programming articles and tutorials on this site are written by Neil Coffey. Suggestions are always welcome if you wish to suggest topics for Java tutorials or programming articles, or if you simply have a programming question that you would like to see answered on this site. Most topics will be considered. But in particular, the site aims to provide tutorials and information on topics that aren't well covered elsewhere, or on Java performance information that is poorly described or understood. Suggestions may be made via the Javamex blog (see the site's front page for details).
Copyright © Javamex UK 2010. All rights reserved.