0

I am new to Java and writing a class to represent Complex numbers.

    //imports

    public final class Complex extends Object implements Serializable{    
        private final double real;
        private final double imaginary;

        public Complex(final double real, final double imaginary){
            this.real=real;
            this.imaginary=imaginary;
        }

        //other constructors

        //other methods

        public Complex multiply(final Complex multiplicand){
            return new Complex(this.real*multiplicand.real-this.imaginary*multiplicand.imaginary,this.real*multiplicand.imaginary+multiplicand.real*this.imaginary);        
        }

        public Complex exponentiate(final Complex exponent){
            return this.logarithm().multiply(exponent).exponentiate();
        }   

        public Complex exponentiate(){
            return new Complex(Math.exp(this.real)*Math.cos(this.imaginary),Math.exp(this.real)*Math.sin(this.imaginary));
        }

        public Complex logarithm(){
            double realPart=Math.log(Math.sqrt(Math.pow(this.real,2)+Math.pow(this.imaginary,2)));
            double imaginaryPart=Math.atan2(this.imaginary,this.real);       
            return new Complex(realPart,imaginaryPart);   
        }

        public static final Complex I=new Complex(0.,1.);     
        public static final Complex E=new Complex(Math.E,0.);
        public static final Complex PI=new Complex(Math.PI,0.);
    }

This is how I attempted to implement (the principle value of) complex exponentiation using Java. However, the objects from the class use double to indicate their real and imaginary parts, and this leads to a serious imprecision. For example, if I attempt System.out.println(Complex.E.exponentiate(Complex.PI.multiply(Complex.I)));, an equivalent to e^(pi*i), the Euler's identity, the result will be -1.0+1.2246467991473532E-16i, but not simply -1.0, and this is due to the imprecision of floating-point variables.

I am wondering if there is any way to calculate a more precise value for exponential functions, either by improving my algorithm or taking an alternate approach to this. Thank you for reading my question.

alainlompo
  • 4,414
  • 4
  • 32
  • 41
  • 2
    That value is very close to the correct exponentiation of the input value—it’s just that that value is not exactly *i* pi. This isn’t a problem that can be fixed other than with different representations (symbolic, arbitrary precision, *etc.*) or with fragile hacks like special-casing certain inputs or discarding all small output components (which contributes more error otherwise). – Davis Herring Jan 01 '20 at 21:10
  • 2
    For higher precision use `java.math.BigDecimal`, but you will still never be able to fit an exact value for either e or pi in a finite computer memory. – rossum Jan 01 '20 at 21:17
  • 1e-16 is pretty darn close to zero for most actual purposes. I had to reword this because I first used most real purposes. – NomadMaker Jan 01 '20 at 21:32
  • 1
    What is your purpose in seeking more accurate results? In general, it is impossible to get exact results, because most results of exponentiation cannot be represented in the floating-point format. So the best you can do is to reduce errors a little more. Why do you need a little more—what is not good enough about the current results, and why would just a little more fix that? If you are just looking for “clean” results for special cases, such as where the result is exactly real, that may be possible. Is it worth it? – Eric Postpischil Jan 01 '20 at 23:17
  • You might want to take a look at some of the libraries for Scala like https://typelevel.org/spire/. – James Moore Jan 01 '20 at 23:22

2 Answers2

0

Using the Math library, there is a limit to what you can get in terms of accuracy. From the docs:

The quality of implementation specifications concern two properties, accuracy of the returned result and monotonicity of the method. Accuracy of the floating-point Math methods is measured in terms of ulps, units in the last place. For a given floating-point format, an ulp of a specific real number value is the distance between the two floating-point values bracketing that numerical value. When discussing the accuracy of a method as a whole rather than at a specific argument, the number of ulps cited is for the worst-case error at any argument. If a method always has an error less than 0.5 ulps, the method always returns the floating-point number nearest the exact result; such a method is correctly rounded. A correctly rounded method is generally the best a floating-point approximation can be; however, it is impractical for many floating-point methods to be correctly rounded. Instead, for the Math class, a larger error bound of 1 or 2 ulps is allowed for certain methods. Informally, with a 1 ulp error bound, when the exact result is a representable number, the exact result should be returned as the computed result; otherwise, either of the two floating-point values which bracket the exact result may be returned. For exact results large in magnitude, one of the endpoints of the bracket may be infinite. Besides accuracy at individual arguments, maintaining proper relations between the method at different arguments is also important. Therefore, most methods with more than 0.5 ulp errors are required to be semi-monotonic: whenever the mathematical function is non-decreasing, so is the floating-point approximation, likewise, whenever the mathematical function is non-increasing, so is the floating-point approximation. Not all approximations that have 1 ulp accuracy will automatically meet the monotonicity requirements.

You can see the ulp limits for each calculation (method) in the docs for the Math class.

You could consider using BigDecimals but there is no equivalent support for the complex math operations except for some publicly available libraries. BigDecimals do allow an unlimited number of bits or precision (within the scope of your computer memory) but are unable to provide both unlimited precision and exact results in all cases. If you calculate 1/3 with unlimited precision, the BigDecimal class will throw an error. For more, see Java BigDecimal trigonometric methods.

Michael McKay
  • 650
  • 4
  • 11
-1

Try Apache Commons Math 3.6.1 API