|
Got a question about Java? Java discussion forum
The cost of Java object orientation in mathematical calculations: introducing a ComplexNumber classOn 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. ImmutabilityNotice 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:
Example outputWith 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 situationsIn 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. Copyright © Javamex UK 2010. All rights reserved. |