blob: 7d6770d932ed5c1e280baff847a49e55bc181355 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.numbers.complex;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.DoubleFunction;
import org.apache.commons.rng.UniformRandomProvider;
import org.apache.commons.rng.simple.RandomSource;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
/**
* Tests for {@link Complex}.
*/
public class ComplexTest {
private static final double inf = Double.POSITIVE_INFINITY;
private static final double neginf = Double.NEGATIVE_INFINITY;
private static final double nan = Double.NaN;
private static final double pi = Math.PI;
private static final Complex oneInf = Complex.ofCartesian(1, inf);
private static final Complex oneNegInf = Complex.ofCartesian(1, neginf);
private static final Complex infOne = Complex.ofCartesian(inf, 1);
private static final Complex infZero = Complex.ofCartesian(inf, 0);
private static final Complex infNegZero = Complex.ofCartesian(inf, -0.0);
private static final Complex infNegInf = Complex.ofCartesian(inf, neginf);
private static final Complex infInf = Complex.ofCartesian(inf, inf);
private static final Complex negInfInf = Complex.ofCartesian(neginf, inf);
private static final Complex negInfOne = Complex.ofCartesian(neginf, 1);
private static final Complex negInfNegInf = Complex.ofCartesian(neginf, neginf);
private static final Complex oneNan = Complex.ofCartesian(1, nan);
private static final Complex zeroInf = Complex.ofCartesian(0, inf);
private static final Complex zeroNan = Complex.ofCartesian(0, nan);
private static final Complex nanZero = Complex.ofCartesian(nan, 0);
private static final Complex NAN = Complex.ofCartesian(nan, nan);
private static final Complex INF = Complex.ofCartesian(inf, inf);
/**
* Used to test the number category of a Complex.
*/
private enum NumberType {
NAN, INFINITE, FINITE
}
/**
* Create a complex number given the real part.
*
* @param real Real part.
* @return {@code Complex} object
*/
private static Complex ofReal(double real) {
return Complex.ofCartesian(real, 0);
}
/**
* Create a complex number given the imaginary part.
*
* @param imaginary Imaginary part.
* @return {@code Complex} object
*/
private static Complex ofImaginary(double imaginary) {
return Complex.ofCartesian(0, imaginary);
}
@Test
public void testCartesianConstructor() {
final Complex z = Complex.ofCartesian(3.0, 4.0);
Assertions.assertEquals(3.0, z.getReal());
Assertions.assertEquals(4.0, z.getImaginary());
}
@Test
public void testPolarConstructor() {
final double r = 98765;
final double theta = 0.12345;
final Complex z = Complex.ofPolar(r, theta);
final Complex y = Complex.ofCis(theta);
Assertions.assertEquals(r * y.getReal(), z.getReal());
Assertions.assertEquals(r * y.getImaginary(), z.getImaginary());
// Edge cases
// Non-finite theta
Assertions.assertEquals(NAN, Complex.ofPolar(1, -inf));
Assertions.assertEquals(NAN, Complex.ofPolar(1, inf));
Assertions.assertEquals(NAN, Complex.ofPolar(1, nan));
// Infinite rho is invalid when theta is NaN
// i.e. do not create an infinite complex such as (inf, nan)
Assertions.assertEquals(NAN, Complex.ofPolar(inf, nan));
// negative or NaN rho
Assertions.assertEquals(NAN, Complex.ofPolar(-inf, 1));
Assertions.assertEquals(NAN, Complex.ofPolar(-0.0, 1));
Assertions.assertEquals(NAN, Complex.ofPolar(nan, 1));
// Construction from infinity has values left to double arithmetic.
// Test the examples from the javadoc
Assertions.assertEquals(NAN, Complex.ofPolar(-0.0, 0.0));
Assertions.assertEquals(Complex.ofCartesian(0.0, 0.0), Complex.ofPolar(0.0, 0.0));
Assertions.assertEquals(Complex.ofCartesian(1.0, 0.0), Complex.ofPolar(1.0, 0.0));
Assertions.assertEquals(Complex.ofCartesian(-1.0, Math.sin(pi)), Complex.ofPolar(1.0, pi));
Assertions.assertEquals(Complex.ofCartesian(-inf, inf), Complex.ofPolar(inf, pi));
Assertions.assertEquals(Complex.ofCartesian(inf, nan), Complex.ofPolar(inf, 0.0));
Assertions.assertEquals(Complex.ofCartesian(inf, -inf), Complex.ofPolar(inf, -pi / 4));
Assertions.assertEquals(Complex.ofCartesian(-inf, -inf), Complex.ofPolar(inf, 5 * pi / 4));
}
@Test
public void testCisConstructor() {
final double x = 0.12345;
final Complex z = Complex.ofCis(x);
Assertions.assertEquals(Math.cos(x), z.getReal());
Assertions.assertEquals(Math.sin(x), z.getImaginary());
}
@Test
public void testNumberType() {
assertNumberType(0, 0, NumberType.FINITE);
assertNumberType(1, 0, NumberType.FINITE);
assertNumberType(0, 1, NumberType.FINITE);
assertNumberType(inf, 0, NumberType.INFINITE);
assertNumberType(-inf, 0, NumberType.INFINITE);
assertNumberType(0, inf, NumberType.INFINITE);
assertNumberType(0, -inf, NumberType.INFINITE);
// A complex or imaginary value with at least one infinite part is regarded as an
// infinity
// (even if its other part is a NaN).
assertNumberType(inf, nan, NumberType.INFINITE);
assertNumberType(-inf, nan, NumberType.INFINITE);
assertNumberType(nan, inf, NumberType.INFINITE);
assertNumberType(nan, -inf, NumberType.INFINITE);
assertNumberType(nan, 0, NumberType.NAN);
assertNumberType(0, nan, NumberType.NAN);
assertNumberType(nan, nan, NumberType.NAN);
}
/**
* Assert the number type of the Complex created from the real and imaginary
* components.
*
* @param real the real component
* @param imaginary the imaginary component
* @param type the type
*/
private static void assertNumberType(double real, double imaginary, NumberType type) {
final Complex z = Complex.ofCartesian(real, imaginary);
final boolean isNaN = z.isNaN();
final boolean isInfinite = z.isInfinite();
final boolean isFinite = z.isFinite();
// A number can be only one
int count = isNaN ? 1 : 0;
count += isInfinite ? 1 : 0;
count += isFinite ? 1 : 0;
Assertions.assertEquals(1, count,
() -> String.format("Complex can be only one type: isNaN=%s, isInfinite=%s, isFinite=%s: %s", isNaN,
isInfinite, isFinite, z));
switch (type) {
case FINITE:
Assertions.assertTrue(isFinite, () -> "not finite: " + z);
break;
case INFINITE:
Assertions.assertTrue(isInfinite, () -> "not infinite: " + z);
break;
case NAN:
Assertions.assertTrue(isNaN, () -> "not nan: " + z);
break;
default:
Assertions.fail("Unknown number type");
}
}
@Test
public void testProj() {
final Complex z = Complex.ofCartesian(3.0, 4.0);
Assertions.assertSame(z, z.proj());
// Sign must be the same for projection
TestUtils.assertSame(infZero, Complex.ofCartesian(inf, 4.0).proj());
TestUtils.assertSame(infZero, Complex.ofCartesian(inf, inf).proj());
TestUtils.assertSame(infZero, Complex.ofCartesian(inf, nan).proj());
TestUtils.assertSame(infZero, Complex.ofCartesian(3.0, inf).proj());
TestUtils.assertSame(infZero, Complex.ofCartesian(nan, inf).proj());
TestUtils.assertSame(infNegZero, Complex.ofCartesian(inf, -4.0).proj());
TestUtils.assertSame(infNegZero, Complex.ofCartesian(inf, -inf).proj());
TestUtils.assertSame(infNegZero, Complex.ofCartesian(3.0, -inf).proj());
TestUtils.assertSame(infNegZero, Complex.ofCartesian(nan, -inf).proj());
}
@Test
public void testAbs() {
final Complex z = Complex.ofCartesian(3.0, 4.0);
Assertions.assertEquals(5.0, z.abs());
}
@Test
public void testAbsNaN() {
// The result is NaN if either argument is NaN and the other is not infinite
Assertions.assertEquals(nan, NAN.abs());
Assertions.assertEquals(nan, Complex.ofCartesian(3.0, nan).abs());
Assertions.assertEquals(nan, Complex.ofCartesian(nan, 3.0).abs());
// The result is positive infinite if either argument is infinite
Assertions.assertEquals(inf, Complex.ofCartesian(inf, nan).abs());
Assertions.assertEquals(inf, Complex.ofCartesian(-inf, nan).abs());
Assertions.assertEquals(inf, Complex.ofCartesian(nan, inf).abs());
Assertions.assertEquals(inf, Complex.ofCartesian(nan, -inf).abs());
}
@Test
public void testNorm() {
final Complex z = Complex.ofCartesian(3.0, 4.0);
Assertions.assertEquals(25.0, z.norm());
}
@Test
public void testNormNaN() {
// The result is NaN if either argument is NaN and the other is not infinite
Assertions.assertEquals(nan, NAN.norm());
Assertions.assertEquals(nan, Complex.ofCartesian(3.0, nan).norm());
Assertions.assertEquals(nan, Complex.ofCartesian(nan, 3.0).norm());
// The result is positive infinite if either argument is infinite
Assertions.assertEquals(inf, Complex.ofCartesian(inf, nan).norm());
Assertions.assertEquals(inf, Complex.ofCartesian(-inf, nan).norm());
Assertions.assertEquals(inf, Complex.ofCartesian(nan, inf).norm());
Assertions.assertEquals(inf, Complex.ofCartesian(nan, -inf).norm());
}
@Test
public void testAdd() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final Complex y = Complex.ofCartesian(5.0, 6.0);
final Complex z = x.add(y);
Assertions.assertEquals(8.0, z.getReal());
Assertions.assertEquals(10.0, z.getImaginary());
}
@Test
public void testAddInf() {
Complex x = Complex.ofCartesian(1, 1);
final Complex z = Complex.ofCartesian(inf, 0);
final Complex w = x.add(z);
Assertions.assertEquals(1, w.getImaginary());
Assertions.assertEquals(inf, w.getReal());
x = Complex.ofCartesian(neginf, 0);
Assertions.assertTrue(Double.isNaN(x.add(z).getReal()));
}
@Test
public void testAddReal() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = 5.0;
final Complex z = x.add(y);
Assertions.assertEquals(8.0, z.getReal());
Assertions.assertEquals(4.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.add(ofReal(y)));
}
@Test
public void testAddRealNaN() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = nan;
final Complex z = x.add(y);
Assertions.assertEquals(nan, z.getReal());
Assertions.assertEquals(4.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.add(ofReal(y)));
}
@Test
public void testAddRealInf() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = inf;
final Complex z = x.add(y);
Assertions.assertEquals(inf, z.getReal());
Assertions.assertEquals(4.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.add(ofReal(y)));
}
@Test
public void testAddRealWithNegZeroImaginary() {
final Complex x = Complex.ofCartesian(3.0, -0.0);
final double y = 5.0;
final Complex z = x.add(y);
Assertions.assertEquals(8.0, z.getReal());
Assertions.assertEquals(-0.0, z.getImaginary(), "Expected sign preservation");
// Sign-preservation is a problem: -0.0 + 0.0 == 0.0
final Complex z2 = x.add(ofReal(y));
Assertions.assertEquals(8.0, z2.getReal());
Assertions.assertEquals(0.0, z2.getImaginary(), "Expected no-sign preservation");
}
@Test
public void testAddImaginary() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = 5.0;
final Complex z = x.addImaginary(y);
Assertions.assertEquals(3.0, z.getReal());
Assertions.assertEquals(9.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.add(ofImaginary(y)));
}
@Test
public void testAddImaginaryNaN() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = nan;
final Complex z = x.addImaginary(y);
Assertions.assertEquals(3.0, z.getReal());
Assertions.assertEquals(nan, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.add(ofImaginary(y)));
}
@Test
public void testAddImaginaryInf() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = inf;
final Complex z = x.addImaginary(y);
Assertions.assertEquals(3.0, z.getReal());
Assertions.assertEquals(inf, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.add(ofImaginary(y)));
}
@Test
public void testAddImaginaryWithNegZeroReal() {
final Complex x = Complex.ofCartesian(-0.0, 4.0);
final double y = 5.0;
final Complex z = x.addImaginary(y);
Assertions.assertEquals(-0.0, z.getReal(), "Expected sign preservation");
Assertions.assertEquals(9.0, z.getImaginary());
// Sign-preservation is a problem: -0.0 + 0.0 == 0.0
final Complex z2 = x.add(ofImaginary(y));
Assertions.assertEquals(0.0, z2.getReal(), "Expected no-sign preservation");
Assertions.assertEquals(9.0, z2.getImaginary());
}
@Test
public void testConjugate() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final Complex z = x.conj();
Assertions.assertEquals(3.0, z.getReal());
Assertions.assertEquals(-4.0, z.getImaginary());
}
@Test
public void testConjugateNaN() {
final Complex z = NAN.conj();
Assertions.assertTrue(z.isNaN());
}
@Test
public void testConjugateInfinite() {
Complex z = Complex.ofCartesian(0, inf);
Assertions.assertEquals(neginf, z.conj().getImaginary());
z = Complex.ofCartesian(0, neginf);
Assertions.assertEquals(inf, z.conj().getImaginary());
}
@Test
public void testDivide() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final Complex y = Complex.ofCartesian(5.0, 6.0);
final Complex z = x.divide(y);
Assertions.assertEquals(39.0 / 61.0, z.getReal());
Assertions.assertEquals(2.0 / 61.0, z.getImaginary());
}
@Test
public void testDivideZero() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final Complex z = x.divide(Complex.ZERO);
Assertions.assertEquals(INF, z);
}
@Test
public void testDivideZeroZero() {
final Complex x = Complex.ofCartesian(0.0, 0.0);
final Complex z = x.divide(Complex.ZERO);
Assertions.assertEquals(NAN, z);
}
@Test
public void testDivideNaN() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final Complex z = x.divide(NAN);
Assertions.assertTrue(z.isNaN());
}
@Test
public void testDivideNanInf() {
Complex z = oneInf.divide(Complex.ONE);
Assertions.assertTrue(Double.isNaN(z.getReal()));
Assertions.assertEquals(inf, z.getImaginary(), 0);
z = negInfNegInf.divide(oneNan);
Assertions.assertTrue(Double.isNaN(z.getReal()));
Assertions.assertTrue(Double.isNaN(z.getImaginary()));
z = negInfInf.divide(Complex.ONE);
Assertions.assertTrue(Double.isInfinite(z.getReal()));
Assertions.assertTrue(Double.isInfinite(z.getImaginary()));
}
@Test
public void testDivideReal() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = 2.0;
Complex z = x.divide(y);
Assertions.assertEquals(1.5, z.getReal());
Assertions.assertEquals(2.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.divide(ofReal(y)));
z = x.divide(-y);
Assertions.assertEquals(-1.5, z.getReal());
Assertions.assertEquals(-2.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.divide(ofReal(-y)));
}
@Test
public void testDivideRealNaN() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = nan;
final Complex z = x.divide(y);
Assertions.assertEquals(nan, z.getReal());
Assertions.assertEquals(nan, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.divide(ofReal(y)));
}
@Test
public void testDivideRealInf() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = inf;
Complex z = x.divide(y);
Assertions.assertEquals(0.0, z.getReal());
Assertions.assertEquals(0.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.divide(ofReal(y)));
z = x.divide(-y);
Assertions.assertEquals(-0.0, z.getReal());
Assertions.assertEquals(-0.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.divide(ofReal(-y)));
}
@Test
public void testDivideRealZero() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = 0.0;
Complex z = x.divide(y);
Assertions.assertEquals(inf, z.getReal());
Assertions.assertEquals(inf, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.divide(ofReal(y)));
z = x.divide(-y);
Assertions.assertEquals(-inf, z.getReal());
Assertions.assertEquals(-inf, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.divide(ofReal(-y)));
}
@Test
public void testDivideImaginary() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = 2.0;
Complex z = x.divideImaginary(y);
Assertions.assertEquals(2.0, z.getReal());
Assertions.assertEquals(-1.5, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.divide(ofImaginary(y)));
z = x.divideImaginary(-y);
Assertions.assertEquals(-2.0, z.getReal());
Assertions.assertEquals(1.5, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.divide(ofImaginary(-y)));
}
@Test
public void testDivideImaginaryNaN() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = nan;
final Complex z = x.divideImaginary(y);
Assertions.assertEquals(nan, z.getReal());
Assertions.assertEquals(nan, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.divide(ofImaginary(y)));
}
@Test
public void testDivideImaginaryInf() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = inf;
Complex z = x.divideImaginary(y);
Assertions.assertEquals(0.0, z.getReal());
Assertions.assertEquals(-0.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.divide(ofImaginary(y)));
z = x.divideImaginary(-y);
Assertions.assertEquals(-0.0, z.getReal());
Assertions.assertEquals(0.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.divide(ofImaginary(-y)));
}
@Test
public void testDivideImaginaryZero() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = 0.0;
Complex z = x.divideImaginary(y);
Assertions.assertEquals(inf, z.getReal());
Assertions.assertEquals(-inf, z.getImaginary());
// Sign-preservation is a problem for imaginary: 0.0 - -0.0 == 0.0
Complex z2 = x.divide(ofImaginary(y));
Assertions.assertEquals(inf, z2.getReal());
Assertions.assertEquals(inf, z2.getImaginary(), "Expected no sign preservation");
z = x.divideImaginary(-y);
Assertions.assertEquals(-inf, z.getReal());
Assertions.assertEquals(inf, z.getImaginary());
// Sign-preservation is a problem for real: 0.0 + -0.0 == 0.0
z2 = x.divide(ofImaginary(-y));
Assertions.assertEquals(inf, z2.getReal(), "Expected no sign preservation");
Assertions.assertEquals(inf, z2.getImaginary());
}
@Test
public void testReciprocal() {
final Complex z = Complex.ofCartesian(5.0, 6.0);
final Complex act = z.reciprocal();
final double expRe = 5.0 / 61.0;
final double expIm = -6.0 / 61.0;
Assertions.assertEquals(expRe, act.getReal(), Math.ulp(expRe));
Assertions.assertEquals(expIm, act.getImaginary(), Math.ulp(expIm));
}
@Test
public void testReciprocalReciprocal() {
final Complex z = Complex.ofCartesian(5.0, 6.0);
final Complex zRR = z.reciprocal().reciprocal();
final double tol = 1e-14;
Assertions.assertEquals(zRR.getReal(), z.getReal(), tol);
Assertions.assertEquals(zRR.getImaginary(), z.getImaginary(), tol);
}
@Test
public void testReciprocalReal() {
final Complex z = Complex.ofCartesian(-2.0, 0.0);
Assertions.assertTrue(Complex.equals(Complex.ofCartesian(-0.5, 0.0), z.reciprocal()));
}
@Test
public void testReciprocalImaginary() {
final Complex z = Complex.ofCartesian(0.0, -2.0);
Assertions.assertEquals(Complex.ofCartesian(0.0, 0.5), z.reciprocal());
}
@Test
public void testReciprocalNaN() {
Assertions.assertTrue(NAN.reciprocal().isNaN());
}
@Test
public void testReciprocalMax() {
// This hits the edge case in reciprocal() for when q != 0 but scale == 0
final double smaller = Math.nextDown(Double.MAX_VALUE);
Complex z = Complex.ofCartesian(smaller, Double.MAX_VALUE);
Assertions.assertEquals(Complex.ofCartesian(0.0, -0.0), z.reciprocal());
z = Complex.ofCartesian(Double.MAX_VALUE, smaller);
Assertions.assertEquals(Complex.ofCartesian(0.0, -0.0), z.reciprocal());
}
@Test
public void testMultiply() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final Complex y = Complex.ofCartesian(5.0, 6.0);
final Complex z = x.multiply(y);
Assertions.assertEquals(-9.0, z.getReal());
Assertions.assertEquals(38.0, z.getImaginary());
}
@Test
public void testMultiplyInfInf() {
final Complex z = infInf.multiply(infInf);
// Assert.assertTrue(z.isNaN()); // MATH-620
Assertions.assertTrue(z.isInfinite());
// Expected results from g++:
Assertions.assertEquals(Complex.ofCartesian(nan, inf), infInf.multiply(infInf));
Assertions.assertEquals(Complex.ofCartesian(inf, nan), infInf.multiply(infNegInf));
Assertions.assertEquals(Complex.ofCartesian(-inf, nan), infInf.multiply(negInfInf));
Assertions.assertEquals(Complex.ofCartesian(nan, -inf), infInf.multiply(negInfNegInf));
}
@Test
public void testMultiplyReal() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = 2.0;
Complex z = x.multiply(y);
Assertions.assertEquals(6.0, z.getReal());
Assertions.assertEquals(8.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.multiply(ofReal(y)));
z = x.multiply(-y);
Assertions.assertEquals(-6.0, z.getReal());
Assertions.assertEquals(-8.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.multiply(ofReal(-y)));
}
@Test
public void testMultiplyRealNaN() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = nan;
final Complex z = x.multiply(y);
Assertions.assertEquals(nan, z.getReal());
Assertions.assertEquals(nan, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.multiply(ofReal(y)));
}
@Test
public void testMultiplyRealInf() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = inf;
Complex z = x.multiply(y);
Assertions.assertEquals(inf, z.getReal());
Assertions.assertEquals(inf, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.multiply(ofReal(y)));
z = x.multiply(-y);
Assertions.assertEquals(-inf, z.getReal());
Assertions.assertEquals(-inf, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.multiply(ofReal(-y)));
}
@Test
public void testMultiplyRealZero() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = 0.0;
Complex z = x.multiply(y);
Assertions.assertEquals(0.0, z.getReal());
Assertions.assertEquals(0.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.multiply(ofReal(y)));
z = x.multiply(-y);
Assertions.assertEquals(-0.0, z.getReal());
Assertions.assertEquals(-0.0, z.getImaginary());
// Sign-preservation is a problem for imaginary: 0.0 - -0.0 == 0.0
final Complex z2 = x.multiply(ofReal(-y));
Assertions.assertEquals(-0.0, z2.getReal());
Assertions.assertEquals(0.0, z2.getImaginary(), "Expected no sign preservation");
}
@Test
public void testMultiplyImaginary() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = 2.0;
Complex z = x.multiplyImaginary(y);
Assertions.assertEquals(-8.0, z.getReal());
Assertions.assertEquals(6.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.multiply(ofImaginary(y)));
z = x.multiplyImaginary(-y);
Assertions.assertEquals(8.0, z.getReal());
Assertions.assertEquals(-6.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.multiply(ofImaginary(-y)));
}
@Test
public void testMultiplyImaginaryNaN() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = nan;
final Complex z = x.multiplyImaginary(y);
Assertions.assertEquals(nan, z.getReal());
Assertions.assertEquals(nan, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.multiply(ofImaginary(y)));
}
@Test
public void testMultiplyImaginaryInf() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = inf;
Complex z = x.multiplyImaginary(y);
Assertions.assertEquals(-inf, z.getReal());
Assertions.assertEquals(inf, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.multiply(ofImaginary(y)));
z = x.multiplyImaginary(-y);
Assertions.assertEquals(inf, z.getReal());
Assertions.assertEquals(-inf, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.multiply(ofImaginary(-y)));
}
@Test
public void testMultiplyImaginaryZero() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = 0.0;
Complex z = x.multiplyImaginary(y);
Assertions.assertEquals(-0.0, z.getReal());
Assertions.assertEquals(0.0, z.getImaginary());
// Sign-preservation is a problem for real: 0.0 - -0.0 == 0.0
Complex z2 = x.multiply(ofImaginary(y));
Assertions.assertEquals(0.0, z2.getReal(), "Expected no sign preservation");
Assertions.assertEquals(0.0, z2.getImaginary());
z = x.multiplyImaginary(-y);
Assertions.assertEquals(0.0, z.getReal());
Assertions.assertEquals(-0.0, z.getImaginary());
// Sign-preservation is a problem for imaginary: -0.0 - 0.0 == 0.0
z2 = x.multiply(ofImaginary(-y));
Assertions.assertEquals(0.0, z2.getReal());
Assertions.assertEquals(0.0, z2.getImaginary(), "Expected no sign preservation");
}
@Test
public void testNonZeroMultiplyI() {
final double[] parts = {3.0, 4.0};
for (final double a : parts) {
for (final double b : parts) {
final Complex c = Complex.ofCartesian(a, b);
final Complex x = c.multiplyImaginary(1.0);
// Check verses algebra solution
Assertions.assertEquals(-b, x.getReal());
Assertions.assertEquals(a, x.getImaginary());
final Complex z = c.multiply(Complex.I);
Assertions.assertEquals(x, z);
}
}
}
@Test
public void testNonZeroMultiplyNegativeI() {
// This works no matter how you represent -I as a Complex
final double[] parts = {3.0, 4.0};
final Complex[] negIs = {Complex.ofCartesian(-0.0, -1), Complex.ofCartesian(0.0, -1)};
for (final double a : parts) {
for (final double b : parts) {
final Complex c = Complex.ofCartesian(a, b);
final Complex x = c.multiplyImaginary(-1.0);
// Check verses algebra solution
Assertions.assertEquals(b, x.getReal());
Assertions.assertEquals(-a, x.getImaginary());
for (final Complex negI : negIs) {
final Complex z = c.multiply(negI);
Assertions.assertEquals(x, z);
}
}
}
}
@Test
public void testZeroMultiplyI() {
final double[] zeros = {-0.0, 0.0};
for (final double a : zeros) {
for (final double b : zeros) {
final Complex c = Complex.ofCartesian(a, b);
final Complex x = c.multiplyImaginary(1.0);
// Check verses algebra solution
Assertions.assertEquals(-b, x.getReal());
Assertions.assertEquals(a, x.getImaginary());
final Complex z = c.multiply(Complex.I);
// Does not work when imaginary part is +0.0.
if (Double.compare(b, 0.0) == 0) {
// (-0.0, 0.0).multiply( (0,1) ) => (-0.0, 0.0) expected (-0.0,-0.0)
// ( 0.0, 0.0).multiply( (0,1) ) => ( 0.0, 0.0) expected (-0.0, 0.0)
Assertions.assertEquals(0, z.getReal(), 0.0);
Assertions.assertEquals(0, z.getImaginary(), 0.0);
Assertions.assertNotEquals(x, z);
} else {
Assertions.assertEquals(x, z);
}
}
}
}
@Test
public void testZeroMultiplyNegativeI() {
// Depending on how we represent -I this does not work for 2/4 cases
// but the cases are different. Here we test the negation of I.
final Complex negI = Complex.I.negate();
final double[] zeros = {-0.0, 0.0};
for (final double a : zeros) {
for (final double b : zeros) {
final Complex c = Complex.ofCartesian(a, b);
final Complex x = c.multiplyImaginary(-1.0);
// Check verses algebra solution
Assertions.assertEquals(b, x.getReal());
Assertions.assertEquals(-a, x.getImaginary());
final Complex z = c.multiply(negI);
final Complex z2 = c.multiply(Complex.I).negate();
// Does not work when imaginary part is -0.0.
if (Double.compare(b, -0.0) == 0) {
// (-0.0,-0.0).multiply( (-0.0,-1) ) => ( 0.0, 0.0) expected (-0.0,
// 0.0)
// ( 0.0,-0.0).multiply( (-0.0,-1) ) => (-0.0, 0.0) expected
// (-0.0,-0.0)
Assertions.assertEquals(0, z.getReal(), 0.0);
Assertions.assertEquals(0, z.getImaginary(), 0.0);
Assertions.assertNotEquals(x, z);
// When multiply by I.negate() fails multiply by I then negate()
// works!
Assertions.assertEquals(x, z2);
} else {
Assertions.assertEquals(x, z);
// When multiply by I.negate() works multiply by I then negate()
// fails!
Assertions.assertNotEquals(x, z2);
}
}
}
}
/**
* Arithmetic test using combinations of +/- x for real, imaginary and and the double
* argument for add, subtract, subtractFrom, multiply and divide, where x is zero or
* non-zero.
*
* <p>The differences to the same argument as a Complex are tested. The only
* differences should be the sign of zero in certain cases.
*/
@Test
public void testSignedArithmetic() {
// The following lists the conditions for the double primitive operation where
// the Complex operation is different. Here the double argument can be:
// x : any value
// +x : positive
// +0.0: positive zero
// -x : negative
// -0.0: negative zero
// 0 : any zero
// use y for any non-zero value
// Check the known fail cases using an integer as a bit set.
// If a bit is 1 then the case is known to fail.
// The 64 cases are enumerated as:
// 4 cases: (a,-0.0) operation on -0.0, 0.0, -2, 3
// 4 cases: (a, 0.0) operation on -0.0, 0.0, -2, 3
// 4 cases: (a,-2.0) operation on -0.0, 0.0, -2, 3
// 4 cases: (a, 3.0) operation on -0.0, 0.0, -2, 3
// with a in [-0.0, 0.0, -2, 3]
// The least significant bit is for the first case.
// The bit set was generated for this test. The summary below demonstrates
// documenting the sign change cases for multiply and divide is non-trivial
// and the javadoc in Complex does not break down the actual cases.
// 16: (x,-0.0) + x
assertSignedZeroArithmetic("addReal", Complex::add, ComplexTest::ofReal, Complex::add,
0b1111000000000000111100000000000011110000000000001111L);
// 16: (-0.0,x) + x
assertSignedZeroArithmetic("addImaginary", Complex::addImaginary, ComplexTest::ofImaginary, Complex::add,
0b1111111111111111L);
// 0:
assertSignedZeroArithmetic("subtractReal", Complex::subtract, ComplexTest::ofReal, Complex::subtract, 0);
// 0:
assertSignedZeroArithmetic("subtractImaginary", Complex::subtractImaginary, ComplexTest::ofImaginary,
Complex::subtract, 0);
// 16: x - (x,+0.0)
assertSignedZeroArithmetic("subtractFromReal", Complex::subtractFrom, ComplexTest::ofReal,
(y, z) -> z.subtract(y), 0b11110000000000001111000000000000111100000000000011110000L);
// 16: x - (+0.0,x)
assertSignedZeroArithmetic("subtractFromImaginary", Complex::subtractFromImaginary, ComplexTest::ofImaginary,
(y, z) -> z.subtract(y), 0b11111111111111110000000000000000L);
// 4: (-0.0,-x) * +x
// 4: (+0.0,-0.0) * x
// 4: (+0.0,x) * -x
// 2: (-y,-x) * +0.0
// 2: (+y,+0.0) * -x
// 2: (+y,-0.0) * +x
// 2: (+y,-x) * -0.0
// 2: (+x,-y) * +0.0
// 2: (+x,+y) * -0.0
assertSignedZeroArithmetic("multiplyReal", Complex::multiply, ComplexTest::ofReal, Complex::multiply,
0b1001101011011000000100000001000010111010111110000101000001010L);
// 4: (-0.0,+x) * +x
// 2: (+0.0,-0.0) * -x
// 4: (+0.0,+0.0) * x
// 2: (+0.0,+y) * -x
// 2: (-y,+x) * +0.0
// 4: (+y,x) * -0.0
// 2: (+0.0,+/-y) * -/+0
// 2: (+y,+/-0.0) * +/-y (sign 0.0 matches sign y)
// 2: (+y,+x) * +0.0
assertSignedZeroArithmetic("multiplyImaginary", Complex::multiplyImaginary, ComplexTest::ofImaginary,
Complex::multiply, 0b11000110110101001000000010000001110001111101011010000010100000L);
// 2: (-0.0,0) / +y
// 2: (+0.0,+x) / -y
// 2: (-x,0) / -y
// 1: (-0.0,+y) / +y
// 1: (-y,+0.0) / -y
assertSignedZeroArithmetic("divideReal", Complex::divide, ComplexTest::ofReal, Complex::divide,
0b100100001000000010000001000000011001000L);
// DivideImaginary has its own test as the result is not always equal ignoring the
// sign.
}
private static void assertSignedZeroArithmetic(String name, BiFunction<Complex, Double, Complex> doubleOperation,
DoubleFunction<Complex> doubleToComplex, BiFunction<Complex, Complex, Complex> complexOperation,
long expectedFailures) {
// With an operation on zero or non-zero arguments
final double[] arguments = {-0.0, 0.0, -2, 3};
for (final double a : arguments) {
for (final double b : arguments) {
final Complex c = Complex.ofCartesian(a, b);
for (final double arg : arguments) {
final Complex y = doubleOperation.apply(c, arg);
final Complex z = complexOperation.apply(c, doubleToComplex.apply(arg));
final boolean expectedFailure = (expectedFailures & 0x1) == 1;
expectedFailures >>>= 1;
// Check the same answer. Sign is allowed to be different for zero.
Assertions.assertEquals(y.getReal(), z.getReal(), 0, () -> c + " " + name + " " + arg + ": real");
Assertions.assertEquals(y.getImaginary(), z.getImaginary(), 0,
() -> c + " " + name + " " + arg + ": imaginary");
Assertions.assertEquals(expectedFailure, !y.equals(z),
() -> c + " " + name + " " + arg + ": sign-difference");
}
}
}
}
/**
* Arithmetic test using combinations of +/- x for real, imaginary and and the double
* argument for divideImaginary, where x is zero or non-zero.
*
* <p>The differences to the same argument as a Complex are tested. This checks for
* sign differences of zero or, if divide by zero, that the result is equal to divide
* by zero using a Complex then multiplied by I.
*/
@Test
public void testDivideImaginaryArithmetic() {
// Cases for divide by non-zero:
// 2: (-0.0,+x) / -y
// 4: (+x,+/-0.0) / -/+y
// 2: (+0.0,+x) / +y
// Cases for divide by zero after multiplication of the Complex result by I:
// 2: (-0.0,+/-y) / +0.0
// 2: (+0.0,+/-y) / +0.0
// 4: (-y,x) / +0.0
// 4: (y,x) / +0.0
// If multiplied by -I all the divide by -0.0 cases have sign errors and / +0.0 is
// OK.
long expectedFailures = 0b11001101111011001100110011001110110011110010000111001101000000L;
// With an operation on zero or non-zero arguments
final double[] arguments = {-0.0, 0.0, -2, 3};
for (final double a : arguments) {
for (final double b : arguments) {
final Complex c = Complex.ofCartesian(a, b);
for (final double arg : arguments) {
final Complex y = c.divideImaginary(arg);
Complex z = c.divide(ofImaginary(arg));
final boolean expectedFailure = (expectedFailures & 0x1) == 1;
expectedFailures >>>= 1;
// If divide by zero then the divide(Complex) method matches divide by
// real.
// To match divide by imaginary requires multiplication by I.
if (arg == 0) {
// Same result if multiplied by I. The sign may not match so
// optionally ignore the sign of the infinity.
z = z.multiplyImaginary(1);
final double ya = expectedFailure ? Math.abs(y.getReal()) : y.getReal();
final double yb = expectedFailure ? Math.abs(y.getImaginary()) : y.getImaginary();
final double za = expectedFailure ? Math.abs(z.getReal()) : z.getReal();
final double zb = expectedFailure ? Math.abs(z.getImaginary()) : z.getImaginary();
Assertions.assertEquals(ya, za, () -> c + " divideImaginary " + arg + ": real");
Assertions.assertEquals(yb, zb, () -> c + " divideImaginary " + arg + ": imaginary");
} else {
// Check the same answer. Sign is allowed to be different for
// zero.
Assertions.assertEquals(y.getReal(), z.getReal(), 0,
() -> c + " divideImaginary " + arg + ": real");
Assertions.assertEquals(y.getImaginary(), z.getImaginary(), 0,
() -> c + " divideImaginary " + arg + ": imaginary");
Assertions.assertEquals(expectedFailure, !y.equals(z),
() -> c + " divideImaginary " + arg + ": sign-difference");
}
}
}
}
}
@Test
public void testNegate() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final Complex z = x.negate();
Assertions.assertEquals(-3.0, z.getReal());
Assertions.assertEquals(-4.0, z.getImaginary());
}
@Test
public void testNegateNaN() {
final Complex z = NAN.negate();
Assertions.assertTrue(z.isNaN());
}
@Test
public void testSubtract() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final Complex y = Complex.ofCartesian(5.0, 7.0);
final Complex z = x.subtract(y);
Assertions.assertEquals(-2.0, z.getReal());
Assertions.assertEquals(-3.0, z.getImaginary());
}
@Test
public void testSubtractInf() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final Complex y = Complex.ofCartesian(inf, 7.0);
Complex z = x.subtract(y);
Assertions.assertEquals(neginf, z.getReal());
Assertions.assertEquals(-3.0, z.getImaginary());
z = y.subtract(y);
Assertions.assertEquals(nan, z.getReal());
Assertions.assertEquals(0.0, z.getImaginary());
}
@Test
public void testSubtractReal() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = 5.0;
final Complex z = x.subtract(y);
Assertions.assertEquals(-2.0, z.getReal());
Assertions.assertEquals(4.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.subtract(ofReal(y)));
}
@Test
public void testSubtractRealNaN() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = nan;
final Complex z = x.subtract(y);
Assertions.assertEquals(nan, z.getReal());
Assertions.assertEquals(4.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.subtract(ofReal(y)));
}
@Test
public void testSubtractRealInf() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = inf;
final Complex z = x.subtract(y);
Assertions.assertEquals(-inf, z.getReal());
Assertions.assertEquals(4.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.subtract(ofReal(y)));
}
@Test
public void testSubtractRealWithNegZeroImaginary() {
final Complex x = Complex.ofCartesian(3.0, -0.0);
final double y = 5.0;
final Complex z = x.subtract(y);
Assertions.assertEquals(-2.0, z.getReal());
Assertions.assertEquals(-0.0, z.getImaginary());
// Equivalent
// Sign-preservation is not a problem: -0.0 - 0.0 == -0.0
Assertions.assertEquals(z, x.subtract(ofReal(y)));
}
@Test
public void testSubtractImaginary() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = 5.0;
final Complex z = x.subtractImaginary(y);
Assertions.assertEquals(3.0, z.getReal());
Assertions.assertEquals(-1.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.subtract(ofImaginary(y)));
}
@Test
public void testSubtractImaginaryNaN() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = nan;
final Complex z = x.subtractImaginary(y);
Assertions.assertEquals(3.0, z.getReal());
Assertions.assertEquals(nan, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.subtract(ofImaginary(y)));
}
@Test
public void testSubtractImaginaryInf() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = inf;
final Complex z = x.subtractImaginary(y);
Assertions.assertEquals(3.0, z.getReal());
Assertions.assertEquals(-inf, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, x.subtract(ofImaginary(y)));
}
@Test
public void testSubtractImaginaryWithNegZeroReal() {
final Complex x = Complex.ofCartesian(-0.0, 4.0);
final double y = 5.0;
final Complex z = x.subtractImaginary(y);
Assertions.assertEquals(-0.0, z.getReal());
Assertions.assertEquals(-1.0, z.getImaginary());
// Equivalent
// Sign-preservation is not a problem: -0.0 - 0.0 == -0.0
Assertions.assertEquals(z, x.subtract(ofImaginary(y)));
}
@Test
public void testSubtractFromReal() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = 5.0;
final Complex z = x.subtractFrom(y);
Assertions.assertEquals(2.0, z.getReal());
Assertions.assertEquals(-4.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, ofReal(y).subtract(x));
}
@Test
public void testSubtractFromRealNaN() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = nan;
final Complex z = x.subtractFrom(y);
Assertions.assertEquals(nan, z.getReal());
Assertions.assertEquals(-4.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, ofReal(y).subtract(x));
}
@Test
public void testSubtractFromRealInf() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = inf;
final Complex z = x.subtractFrom(y);
Assertions.assertEquals(inf, z.getReal());
Assertions.assertEquals(-4.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, ofReal(y).subtract(x));
}
@Test
public void testSubtractFromRealWithPosZeroImaginary() {
final Complex x = Complex.ofCartesian(3.0, 0.0);
final double y = 5.0;
final Complex z = x.subtractFrom(y);
Assertions.assertEquals(2.0, z.getReal());
Assertions.assertEquals(-0.0, z.getImaginary(), "Expected sign inversion");
// Sign-inversion is a problem: 0.0 - 0.0 == 0.0
Assertions.assertNotEquals(z, ofReal(y).subtract(x));
}
@Test
public void testSubtractFromImaginary() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = 5.0;
final Complex z = x.subtractFromImaginary(y);
Assertions.assertEquals(-3.0, z.getReal());
Assertions.assertEquals(1.0, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, ofImaginary(y).subtract(x));
}
@Test
public void testSubtractFromImaginaryNaN() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = nan;
final Complex z = x.subtractFromImaginary(y);
Assertions.assertEquals(-3.0, z.getReal());
Assertions.assertEquals(nan, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, ofImaginary(y).subtract(x));
}
@Test
public void testSubtractFromImaginaryInf() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final double y = inf;
final Complex z = x.subtractFromImaginary(y);
Assertions.assertEquals(-3.0, z.getReal());
Assertions.assertEquals(inf, z.getImaginary());
// Equivalent
Assertions.assertEquals(z, ofImaginary(y).subtract(x));
}
@Test
public void testSubtractFromImaginaryWithPosZeroReal() {
final Complex x = Complex.ofCartesian(0.0, 4.0);
final double y = 5.0;
final Complex z = x.subtractFromImaginary(y);
Assertions.assertEquals(-0.0, z.getReal(), "Expected sign inversion");
Assertions.assertEquals(1.0, z.getImaginary());
// Sign-inversion is a problem: 0.0 - 0.0 == 0.0
Assertions.assertNotEquals(z, ofImaginary(y).subtract(x));
}
@Test
public void testFloatingPointEqualsPrecondition1() {
Assertions.assertThrows(NullPointerException.class,
() -> Complex.equals(Complex.ofCartesian(3.0, 4.0), null, 3));
}
@Test
public void testFloatingPointEqualsPrecondition2() {
Assertions.assertThrows(NullPointerException.class,
() -> Complex.equals(null, Complex.ofCartesian(3.0, 4.0), 3));
}
@Test
public void testFloatingPointEquals() {
double re = -3.21;
double im = 456789e10;
final Complex x = Complex.ofCartesian(re, im);
Complex y = Complex.ofCartesian(re, im);
Assertions.assertEquals(x, y);
Assertions.assertTrue(Complex.equals(x, y));
final int maxUlps = 5;
for (int i = 0; i < maxUlps; i++) {
re = Math.nextUp(re);
im = Math.nextUp(im);
}
y = Complex.ofCartesian(re, im);
Assertions.assertTrue(Complex.equals(x, y, maxUlps));
re = Math.nextUp(re);
im = Math.nextUp(im);
y = Complex.ofCartesian(re, im);
Assertions.assertFalse(Complex.equals(x, y, maxUlps));
}
@Test
public void testFloatingPointEqualsNaN() {
Complex c = Complex.ofCartesian(Double.NaN, 1);
Assertions.assertFalse(Complex.equals(c, c));
c = Complex.ofCartesian(1, Double.NaN);
Assertions.assertFalse(Complex.equals(c, c));
}
@Test
public void testFloatingPointEqualsWithAllowedDelta() {
final double re = 153.0000;
final double im = 152.9375;
final double tol1 = 0.0625;
final Complex x = Complex.ofCartesian(re, im);
final Complex y = Complex.ofCartesian(re + tol1, im + tol1);
Assertions.assertTrue(Complex.equals(x, y, tol1));
final double tol2 = 0.0624;
Assertions.assertFalse(Complex.equals(x, y, tol2));
}
@Test
public void testFloatingPointEqualsWithAllowedDeltaNaN() {
final Complex x = Complex.ofCartesian(0, Double.NaN);
final Complex y = Complex.ofCartesian(Double.NaN, 0);
Assertions.assertFalse(Complex.equals(x, Complex.ZERO, 0.1));
Assertions.assertFalse(Complex.equals(x, x, 0.1));
Assertions.assertFalse(Complex.equals(x, y, 0.1));
}
@Test
public void testFloatingPointEqualsWithRelativeTolerance() {
final double tol = 1e-4;
final double re = 1;
final double im = 1e10;
final double f = 1 + tol;
final Complex x = Complex.ofCartesian(re, im);
final Complex y = Complex.ofCartesian(re * f, im * f);
Assertions.assertTrue(Complex.equalsWithRelativeTolerance(x, y, tol));
}
@Test
public void testFloatingPointEqualsWithRelativeToleranceNan() {
final Complex x = Complex.ofCartesian(0, Double.NaN);
final Complex y = Complex.ofCartesian(Double.NaN, 0);
Assertions.assertFalse(Complex.equalsWithRelativeTolerance(x, Complex.ZERO, 0.1));
Assertions.assertFalse(Complex.equalsWithRelativeTolerance(x, x, 0.1));
Assertions.assertFalse(Complex.equalsWithRelativeTolerance(x, y, 0.1));
}
@Test
public void testEqualsWithNull() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
Assertions.assertFalse(x.equals(null));
}
@Test
public void testEqualsWithAnotherClass() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
Assertions.assertFalse(x.equals(new Object()));
}
@Test
public void testEqualsWithSameObject() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
Assertions.assertEquals(x, x);
}
@Test
public void testEqualsWithCopyObject() {
final Complex x = Complex.ofCartesian(3.0, 4.0);
final Complex y = Complex.ofCartesian(3.0, 4.0);
Assertions.assertEquals(x, y);
}
@Test
public void testEqualsWithRealDifference() {
final Complex x = Complex.ofCartesian(0.0, 0.0);
final Complex y = Complex.ofCartesian(0.0 + Double.MIN_VALUE, 0.0);
Assertions.assertNotEquals(x, y);
}
@Test
public void testEqualsWithImaginaryDifference() {
final Complex x = Complex.ofCartesian(0.0, 0.0);
final Complex y = Complex.ofCartesian(0.0, 0.0 + Double.MIN_VALUE);
Assertions.assertNotEquals(x, y);
}
/**
* Test {@link Complex#equals(Object)}. It should be consistent with
* {@link Arrays#equals(double[], double[])} called using the components of two
* complex numbers.
*/
@Test
public void testEqualsIsConsistentWithArraysEquals() {
// Explicit check of the cases documented in the Javadoc:
assertEqualsIsConsistentWithArraysEquals(Complex.ofCartesian(Double.NaN, 0.0),
Complex.ofCartesian(Double.NaN, 1.0), "NaN real and different non-NaN imaginary");
assertEqualsIsConsistentWithArraysEquals(Complex.ofCartesian(0.0, Double.NaN),
Complex.ofCartesian(1.0, Double.NaN), "Different non-NaN real and NaN imaginary");
assertEqualsIsConsistentWithArraysEquals(Complex.ofCartesian(0.0, 0.0), Complex.ofCartesian(-0.0, 0.0),
"Different real zeros");
assertEqualsIsConsistentWithArraysEquals(Complex.ofCartesian(0.0, 0.0), Complex.ofCartesian(0.0, -0.0),
"Different imaginary zeros");
// Test some values of edge cases
final double[] values = {Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, -1, 0, 1};
final ArrayList<Complex> list = createCombinations(values);
for (final Complex c : list) {
final double real = c.getReal();
final double imag = c.getImaginary();
// Check a copy is equal
assertEqualsIsConsistentWithArraysEquals(c, Complex.ofCartesian(real, imag), "Copy complex");
// Perform the smallest change to the two components
final double realDelta = smallestChange(real);
final double imagDelta = smallestChange(imag);
Assertions.assertNotEquals(real, realDelta, "Real was not changed");
Assertions.assertNotEquals(imag, imagDelta, "Imaginary was not changed");
assertEqualsIsConsistentWithArraysEquals(c, Complex.ofCartesian(realDelta, imag), "Delta real");
assertEqualsIsConsistentWithArraysEquals(c, Complex.ofCartesian(real, imagDelta), "Delta imaginary");
}
}
/**
* Specific test to target different representations that return {@code true} for
* {@link Complex#isNaN()} are {@code false} for {@link Complex#equals(Object)}.
*/
@Test
public void testEqualsWithDifferentNaNs() {
// Test some NaN combinations
final double[] values = {Double.NaN, 0, 1};
final ArrayList<Complex> list = createCombinations(values);
// Is the all-vs-all comparison only the exact same values should be equal, e.g.
// (nan,0) not equals (nan,nan)
// (nan,0) equals (nan,0)
// (nan,0) not equals (0,nan)
for (int i = 0; i < list.size(); i++) {
final Complex c1 = list.get(i);
final Complex copy = Complex.ofCartesian(c1.getReal(), c1.getImaginary());
assertEqualsIsConsistentWithArraysEquals(c1, copy, "Copy is not equal");
for (int j = i + 1; j < list.size(); j++) {
final Complex c2 = list.get(j);
assertEqualsIsConsistentWithArraysEquals(c1, c2, "Different NaNs should not be equal");
}
}
}
/**
* Test the two complex numbers with {@link Complex#equals(Object)} and check the
* result is consistent with {@link Arrays#equals(double[], double[])}.
*
* @param c1 the first complex
* @param c2 the second complex
* @param msg the message to append to an assertion error
*/
private static void assertEqualsIsConsistentWithArraysEquals(Complex c1, Complex c2, String msg) {
final boolean expected = Arrays.equals(new double[] {c1.getReal(), c1.getImaginary()},
new double[] {c2.getReal(), c2.getImaginary()});
final boolean actual = c1.equals(c2);
Assertions.assertEquals(expected, actual,
() -> String.format("equals(Object) is not consistent with Arrays.equals: %s. %s vs %s", msg, c1, c2));
}
/**
* Test {@link Complex#hashCode()}. It should be consistent with
* {@link Arrays#hashCode(double[])} called using the components of the complex number
* and fulfil the contract of {@link Object#hashCode()}, i.e. objects with different
* hash codes are {@code false} for {@link Object#equals(Object)}.
*/
@Test
public void testHashCode() {
// Test some values match Arrays.hashCode(double[])
final double[] values = {Double.NaN, Double.NEGATIVE_INFINITY, -3.45, -1, -0.0, 0.0, Double.MIN_VALUE, 1, 3.45,
Double.POSITIVE_INFINITY};
final ArrayList<Complex> list = createCombinations(values);
final String msg = "'equals' not compatible with 'hashCode'";
for (final Complex c : list) {
final double real = c.getReal();
final double imag = c.getImaginary();
final int expected = Arrays.hashCode(new double[] {real, imag});
final int hash = c.hashCode();
Assertions.assertEquals(expected, hash, "hashCode does not match Arrays.hashCode({re, im})");
// Test a copy has the same hash code, i.e. is not
// System.identityHashCode(Object)
final Complex copy = Complex.ofCartesian(real, imag);
Assertions.assertEquals(hash, copy.hashCode(), "Copy hash code is not equal");
// MATH-1118
// "equals" and "hashCode" must be compatible: if two objects have
// different hash codes, "equals" must return false.
// Perform the smallest change to the two components.
// Note: The hash could actually be the same so we check it changes.
final double realDelta = smallestChange(real);
final double imagDelta = smallestChange(imag);
Assertions.assertNotEquals(real, realDelta, "Real was not changed");
Assertions.assertNotEquals(imag, imagDelta, "Imaginary was not changed");
final Complex cRealDelta = Complex.ofCartesian(realDelta, imag);
final Complex cImagDelta = Complex.ofCartesian(real, imagDelta);
if (hash != cRealDelta.hashCode()) {
Assertions.assertNotEquals(c, cRealDelta, () -> "real+delta: " + msg);
}
if (hash != cImagDelta.hashCode()) {
Assertions.assertNotEquals(c, cImagDelta, () -> "imaginary+delta: " + msg);
}
}
}
/**
* Specific test that different representations of zero satisfy the contract of
* {@link Object#hashCode()}: if two objects have different hash codes, "equals" must
* return false. This is an issue with using {@link Double#hashCode(double)} to create
* hash codes and {@code ==} for equality when using different representations of
* zero: Double.hashCode(-0.0) != Double.hashCode(0.0) but -0.0 == 0.0 is
* {@code true}.
*
* @see <a
* href="https://issues.apache.org/jira/projects/MATH/issues/MATH-1118">MATH-1118</a>
*/
@Test
public void testHashCodeWithDifferentZeros() {
final double[] values = {-0.0, 0.0};
final ArrayList<Complex> list = createCombinations(values);
// Explicit test for issue MATH-1118
// "equals" and "hashCode" must be compatible
for (int i = 0; i < list.size(); i++) {
final Complex c1 = list.get(i);
for (int j = i + 1; j < list.size(); j++) {
final Complex c2 = list.get(j);
if (c1.hashCode() != c2.hashCode()) {
Assertions.assertNotEquals(c1, c2, "'equals' not compatible with 'hashCode'");
}
}
}
}
/**
* Creates a list of Complex numbers using an all-vs-all combination of the provided
* values for both the real and imaginary parts.
*
* @param values the values
* @return the list
*/
private static ArrayList<Complex> createCombinations(final double[] values) {
final ArrayList<Complex> list = new ArrayList<>(values.length * values.length);
for (final double re : values) {
for (final double im : values) {
list.add(Complex.ofCartesian(re, im));
}
}
return list;
}
/**
* Perform the smallest change to the value. This returns the next double value
* adjacent to d in the direction of infinity. Edge cases: if already infinity then
* return the next closest in the direction of negative infinity; if nan then return
* 0.
*
* @param x the x
* @return the new value
*/
private static double smallestChange(double x) {
if (Double.isNaN(x)) {
return 0;
}
return x == Double.POSITIVE_INFINITY ? Math.nextDown(x) : Math.nextUp(x);
}
@Test
@Disabled("Used to output the java environment")
@SuppressWarnings("squid:S2699")
public void testJava() {
// CHECKSTYLE: stop Regexp
System.out.println(">>testJava()");
// MathTest#testExpSpecialCases() checks the following:
// Assert.assertEquals("exp of -infinity should be 0.0", 0.0,
// Math.exp(Double.NEGATIVE_INFINITY), Precision.EPSILON);
// Let's check how well Math works:
System.out.println("Math.exp=" + Math.exp(Double.NEGATIVE_INFINITY));
final String[] props = {"java.version", // Java Runtime Environment version
"java.vendor", // Java Runtime Environment vendor
"java.vm.specification.version", // Java Virtual Machine specification version
"java.vm.specification.vendor", // Java Virtual Machine specification vendor
"java.vm.specification.name", // Java Virtual Machine specification name
"java.vm.version", // Java Virtual Machine implementation version
"java.vm.vendor", // Java Virtual Machine implementation vendor
"java.vm.name", // Java Virtual Machine implementation name
"java.specification.version", // Java Runtime Environment specification
// version
"java.specification.vendor", // Java Runtime Environment specification vendor
"java.specification.name", // Java Runtime Environment specification name
"java.class.version", // Java class format version number
};
for (final String t : props) {
System.out.println(t + "=" + System.getProperty(t));
}
System.out.println("<<testJava()");
// CHECKSTYLE: resume Regexp
}
@Test
public void testPow() {
final Complex x = Complex.ofCartesian(3, 4);
final double yDouble = 5.0;
final Complex yComplex = ofReal(yDouble);
Assertions.assertEquals(x.pow(yComplex), x.pow(yDouble));
}
@Test
public void testPowComplexRealZero() {
// Hits the edge case when real == 0 but imaginary != 0
final Complex x = Complex.ofCartesian(0, 1);
final Complex z = Complex.ofCartesian(2, 3);
final Complex c = x.pow(z);
// Answer from g++
Assertions.assertEquals(-0.008983291021129429, c.getReal());
Assertions.assertEquals(1.1001358594835313e-18, c.getImaginary());
}
@Test
public void testPowComplexZeroBase() {
final double x = Double.MIN_VALUE;
assertPowComplexZeroBase(0, 0, NAN);
assertPowComplexZeroBase(0, x, NAN);
assertPowComplexZeroBase(x, x, NAN);
assertPowComplexZeroBase(x, 0, Complex.ZERO);
}
private static void assertPowComplexZeroBase(double re, double im, Complex expected) {
final Complex z = Complex.ofCartesian(re, im);
final Complex c = Complex.ZERO.pow(z);
Assertions.assertEquals(expected, c);
}
@Test
public void testPowScalerRealZero() {
// Hits the edge case when real == 0 but imaginary != 0
final Complex x = Complex.ofCartesian(0, 1);
final Complex c = x.pow(2);
// Answer from g++
Assertions.assertEquals(-1, c.getReal());
Assertions.assertEquals(1.2246467991473532e-16, c.getImaginary());
}
@Test
public void testPowScalarZeroBase() {
final double x = Double.MIN_VALUE;
assertPowScalarZeroBase(0, NAN);
assertPowScalarZeroBase(x, Complex.ZERO);
}
private static void assertPowScalarZeroBase(double exp, Complex expected) {
final Complex c = Complex.ZERO.pow(exp);
Assertions.assertEquals(expected, c);
}
@Test
public void testPowNanBase() {
final Complex x = NAN;
final double yDouble = 5.0;
final Complex yComplex = ofReal(yDouble);
Assertions.assertEquals(x.pow(yComplex), x.pow(yDouble));
}
@Test
public void testPowNanExponent() {
final Complex x = Complex.ofCartesian(3, 4);
final double yDouble = Double.NaN;
final Complex yComplex = ofReal(yDouble);
Assertions.assertEquals(x.pow(yComplex), x.pow(yDouble));
}
@Test
public void testSqrtPolar() {
final double tol = 1e-12;
double r = 1;
for (int i = 0; i < 5; i++) {
r += i;
double theta = 0;
for (int j = 0; j < 11; j++) {
theta += pi / 12;
final Complex z = Complex.ofPolar(r, theta);
final Complex sqrtz = Complex.ofPolar(Math.sqrt(r), theta / 2);
TestUtils.assertEquals(sqrtz, z.sqrt(), tol);
}
}
}
@Test
public void testZerothRootThrows() {
final Complex c = Complex.ofCartesian(1, 1);
Assertions.assertThrows(IllegalArgumentException.class, () -> c.nthRoot(0),
"zeroth root should not be allowed");
}
/**
* Test: computing <b>third roots</b> of z.
*
* <pre>
* <code>
* <b>z = -2 + 2 * i</b>
* => z_0 = 1 + i
* => z_1 = -1.3660 + 0.3660 * i
* => z_2 = 0.3660 - 1.3660 * i
* </code>
* </pre>
*/
@Test
public void testNthRootNormalThirdRoot() {
// The complex number we want to compute all third-roots for.
final Complex z = Complex.ofCartesian(-2, 2);
// The List holding all third roots
final Complex[] thirdRootsOfZ = z.nthRoot(3).toArray(new Complex[0]);
// Returned Collection must not be empty!
Assertions.assertEquals(3, thirdRootsOfZ.length);
// test z_0
Assertions.assertEquals(1.0, thirdRootsOfZ[0].getReal(), 1.0e-5);
Assertions.assertEquals(1.0, thirdRootsOfZ[0].getImaginary(), 1.0e-5);
// test z_1
Assertions.assertEquals(-1.3660254037844386, thirdRootsOfZ[1].getReal(), 1.0e-5);
Assertions.assertEquals(0.36602540378443843, thirdRootsOfZ[1].getImaginary(), 1.0e-5);
// test z_2
Assertions.assertEquals(0.366025403784439, thirdRootsOfZ[2].getReal(), 1.0e-5);
Assertions.assertEquals(-1.3660254037844384, thirdRootsOfZ[2].getImaginary(), 1.0e-5);
}
/**
* Test: computing <b>fourth roots</b> of z.
*
* <pre>
* <code>
* <b>z = 5 - 2 * i</b>
* => z_0 = 1.5164 - 0.1446 * i
* => z_1 = 0.1446 + 1.5164 * i
* => z_2 = -1.5164 + 0.1446 * i
* => z_3 = -1.5164 - 0.1446 * i
* </code>
* </pre>
*/
@Test
public void testNthRootNormalFourthRoot() {
// The complex number we want to compute all third-roots for.
final Complex z = Complex.ofCartesian(5, -2);
// The List holding all fourth roots
final Complex[] fourthRootsOfZ = z.nthRoot(4).toArray(new Complex[0]);
// Returned Collection must not be empty!
Assertions.assertEquals(4, fourthRootsOfZ.length);
// test z_0
Assertions.assertEquals(1.5164629308487783, fourthRootsOfZ[0].getReal(), 1.0e-5);
Assertions.assertEquals(-0.14469266210702247, fourthRootsOfZ[0].getImaginary(), 1.0e-5);
// test z_1
Assertions.assertEquals(0.14469266210702256, fourthRootsOfZ[1].getReal(), 1.0e-5);
Assertions.assertEquals(1.5164629308487783, fourthRootsOfZ[1].getImaginary(), 1.0e-5);
// test z_2
Assertions.assertEquals(-1.5164629308487783, fourthRootsOfZ[2].getReal(), 1.0e-5);
Assertions.assertEquals(0.14469266210702267, fourthRootsOfZ[2].getImaginary(), 1.0e-5);
// test z_3
Assertions.assertEquals(-0.14469266210702275, fourthRootsOfZ[3].getReal(), 1.0e-5);
Assertions.assertEquals(-1.5164629308487783, fourthRootsOfZ[3].getImaginary(), 1.0e-5);
}
/**
* Test: computing <b>third roots</b> of z.
*
* <pre>
* <code>
* <b>z = 8</b>
* => z_0 = 2
* => z_1 = -1 + 1.73205 * i
* => z_2 = -1 - 1.73205 * i
* </code>
* </pre>
*/
@Test
public void testNthRootCornercaseThirdRootImaginaryPartEmpty() {
// The number 8 has three third roots. One we all already know is the number 2.
// But there are two more complex roots.
final Complex z = Complex.ofCartesian(8, 0);
// The List holding all third roots
final Complex[] thirdRootsOfZ = z.nthRoot(3).toArray(new Complex[0]);
// Returned Collection must not be empty!
Assertions.assertEquals(3, thirdRootsOfZ.length);
// test z_0
Assertions.assertEquals(2.0, thirdRootsOfZ[0].getReal(), 1.0e-5);
Assertions.assertEquals(0.0, thirdRootsOfZ[0].getImaginary(), 1.0e-5);
// test z_1
Assertions.assertEquals(-1.0, thirdRootsOfZ[1].getReal(), 1.0e-5);
Assertions.assertEquals(1.7320508075688774, thirdRootsOfZ[1].getImaginary(), 1.0e-5);
// test z_2
Assertions.assertEquals(-1.0, thirdRootsOfZ[2].getReal(), 1.0e-5);
Assertions.assertEquals(-1.732050807568877, thirdRootsOfZ[2].getImaginary(), 1.0e-5);
}
/**
* Test: computing <b>third roots</b> of z with real part 0.
*
* <pre>
* <code>
* <b>z = 2 * i</b>
* => z_0 = 1.0911 + 0.6299 * i
* => z_1 = -1.0911 + 0.6299 * i
* => z_2 = -2.3144 - 1.2599 * i
* </code>
* </pre>
*/
@Test
public void testNthRootCornercaseThirdRootRealPartZero() {
// complex number with only imaginary part
final Complex z = Complex.ofCartesian(0, 2);
// The List holding all third roots
final Complex[] thirdRootsOfZ = z.nthRoot(3).toArray(new Complex[0]);
// Returned Collection must not be empty!
Assertions.assertEquals(3, thirdRootsOfZ.length);
// test z_0
Assertions.assertEquals(1.0911236359717216, thirdRootsOfZ[0].getReal(), 1.0e-5);
Assertions.assertEquals(0.6299605249474365, thirdRootsOfZ[0].getImaginary(), 1.0e-5);
// test z_1
Assertions.assertEquals(-1.0911236359717216, thirdRootsOfZ[1].getReal(), 1.0e-5);
Assertions.assertEquals(0.6299605249474365, thirdRootsOfZ[1].getImaginary(), 1.0e-5);
// test z_2
Assertions.assertEquals(-2.3144374213981936E-16, thirdRootsOfZ[2].getReal(), 1.0e-5);
Assertions.assertEquals(-1.2599210498948732, thirdRootsOfZ[2].getImaginary(), 1.0e-5);
}
/**
* Test: compute <b>third roots</b> using a negative argument to go clockwise around
* the unit circle. Fourth roots of one are taken in both directions around the circle
* using positive and negative arguments.
*
* <pre>
* <code>
* <b>z = 1</b>
* => z_0 = Positive: 1,0 ; Negative: 1,0
* => z_1 = Positive: 0,1 ; Negative: 0,-1
* => z_2 = Positive: -1,0 ; Negative: -1,0
* => z_3 = Positive: 0,-1 ; Negative: 0,1
* </code>
* </pre>
*/
@Test
public void testNthRootNegativeArg() {
// The complex number we want to compute all third-roots for.
final Complex z = Complex.ofCartesian(1, 0);
// The List holding all fourth roots
Complex[] fourthRootsOfZ = z.nthRoot(4).toArray(new Complex[0]);
// test z_0
Assertions.assertEquals(1, fourthRootsOfZ[0].getReal(), 1.0e-5);
Assertions.assertEquals(0, fourthRootsOfZ[0].getImaginary(), 1.0e-5);
// test z_1
Assertions.assertEquals(0, fourthRootsOfZ[1].getReal(), 1.0e-5);
Assertions.assertEquals(1, fourthRootsOfZ[1].getImaginary(), 1.0e-5);
// test z_2
Assertions.assertEquals(-1, fourthRootsOfZ[2].getReal(), 1.0e-5);
Assertions.assertEquals(0, fourthRootsOfZ[2].getImaginary(), 1.0e-5);
// test z_3
Assertions.assertEquals(0, fourthRootsOfZ[3].getReal(), 1.0e-5);
Assertions.assertEquals(-1, fourthRootsOfZ[3].getImaginary(), 1.0e-5);
// go clockwise around the unit circle using negative argument
fourthRootsOfZ = z.nthRoot(-4).toArray(new Complex[0]);
// test z_0
Assertions.assertEquals(1, fourthRootsOfZ[0].getReal(), 1.0e-5);
Assertions.assertEquals(0, fourthRootsOfZ[0].getImaginary(), 1.0e-5);
// test z_1
Assertions.assertEquals(0, fourthRootsOfZ[1].getReal(), 1.0e-5);
Assertions.assertEquals(-1, fourthRootsOfZ[1].getImaginary(), 1.0e-5);
// test z_2
Assertions.assertEquals(-1, fourthRootsOfZ[2].getReal(), 1.0e-5);
Assertions.assertEquals(0, fourthRootsOfZ[2].getImaginary(), 1.0e-5);
// test z_3
Assertions.assertEquals(0, fourthRootsOfZ[3].getReal(), 1.0e-5);
Assertions.assertEquals(1, fourthRootsOfZ[3].getImaginary(), 1.0e-5);
}
@Test
public void testNthRootNan() {
final int n = 3;
final Complex z = ofReal(Double.NaN);
final List<Complex> r = z.nthRoot(n);
Assertions.assertEquals(n, r.size());
for (final Complex c : r) {
Assertions.assertTrue(Double.isNaN(c.getReal()));
Assertions.assertTrue(Double.isNaN(c.getImaginary()));
}
}
@Test
public void testNthRootInf() {
final int n = 3;
final Complex z = ofReal(Double.NEGATIVE_INFINITY);
final List<Complex> r = z.nthRoot(n);
Assertions.assertEquals(n, r.size());
}
/**
* Test standard values
*/
@Test
public void testGetArgument() {
Complex z = Complex.ofCartesian(1, 0);
assertGetArgument(0.0, z, 1.0e-12);
z = Complex.ofCartesian(1, 1);
assertGetArgument(Math.PI / 4, z, 1.0e-12);
z = Complex.ofCartesian(0, 1);
assertGetArgument(Math.PI / 2, z, 1.0e-12);
z = Complex.ofCartesian(-1, 1);
assertGetArgument(3 * Math.PI / 4, z, 1.0e-12);
z = Complex.ofCartesian(-1, 0);
assertGetArgument(Math.PI, z, 1.0e-12);
z = Complex.ofCartesian(-1, -1);
assertGetArgument(-3 * Math.PI / 4, z, 1.0e-12);
z = Complex.ofCartesian(0, -1);
assertGetArgument(-Math.PI / 2, z, 1.0e-12);
z = Complex.ofCartesian(1, -1);
assertGetArgument(-Math.PI / 4, z, 1.0e-12);
}
/**
* Verify atan2-style handling of infinite parts
*/
@Test
public void testGetArgumentInf() {
assertGetArgument(Math.PI / 4, infInf, 1.0e-12);
assertGetArgument(Math.PI / 2, oneInf, 1.0e-12);
assertGetArgument(0.0, infOne, 1.0e-12);
assertGetArgument(Math.PI / 2, zeroInf, 1.0e-12);
assertGetArgument(0.0, infZero, 1.0e-12);
assertGetArgument(Math.PI, negInfOne, 1.0e-12);
assertGetArgument(-3.0 * Math.PI / 4, negInfNegInf, 1.0e-12);
assertGetArgument(-Math.PI / 2, oneNegInf, 1.0e-12);
}
/**
* Verify that either part NaN results in NaN
*/
@Test
public void testGetArgumentNaN() {
assertGetArgument(Double.NaN, nanZero, 0);
assertGetArgument(Double.NaN, zeroNan, 0);
assertGetArgument(Double.NaN, NAN, 0);
}
private static void assertGetArgument(double expected, Complex complex, double delta) {
final double actual = complex.arg();
Assertions.assertEquals(expected, actual, delta);
Assertions.assertEquals(actual, complex.arg(), delta);
}
@Test
public void testParse() {
final double[] parts = {Double.NEGATIVE_INFINITY, -1, -0.0, 0.0, 1, Math.PI, Double.POSITIVE_INFINITY,
Double.NaN};
for (final double x : parts) {
for (final double y : parts) {
final Complex z = Complex.ofCartesian(x, y);
Assertions.assertEquals(z, Complex.parse(z.toString()));
}
}
final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
for (int i = 0; i < 10; i++) {
final double x = -1 + rng.nextDouble() * 2;
final double y = -1 + rng.nextDouble() * 2;
final Complex z = Complex.ofCartesian(x, y);
Assertions.assertEquals(z, Complex.parse(z.toString()));
}
// Special values not covered
Assertions.assertEquals(Complex.ofPolar(2, pi), Complex.parse(Complex.ofPolar(2, pi).toString()));
Assertions.assertEquals(Complex.ofCis(pi), Complex.parse(Complex.ofCis(pi).toString()));
}
@Test
public void testParseNull() {
Assertions.assertThrows(NullPointerException.class, () -> Complex.parse(null));
}
@Test
public void testParseEmpty() {
Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse(""));
Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse(" "));
}
@Test
public void testParseWrongStart() {
Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("1.0,2.0)"));
Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("[1.0,2.0)"));
}
@Test
public void testParseWrongEnd() {
Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,2.0"));
Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,2.0]"));
}
@Test
public void testParseWrongSeparator() {
Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0 2.0)"));
Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0:2.0)"));
}
@Test
public void testParseSeparatorOutsideStartAndEnd() {
Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,2.0),"));
Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse(",(1.0,2.0)"));
}
@Test
public void testParseExtraSeparator() {
Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,,2.0)"));
Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,2.0,)"));
Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(,1.0,2.0)"));
Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,2,0)"));
}
@Test
public void testParseInvalidRe() {
Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(I.0,2.0)"));
}
@Test
public void testParseInvalidIm() {
Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,2.G)"));
}
@Test
public void testParseSpaceAllowedAroundNumbers() {
final double re = 1.234;
final double im = 5.678;
final Complex z = Complex.ofCartesian(re, im);
Assertions.assertEquals(z, Complex.parse("(" + re + "," + im + ")"));
Assertions.assertEquals(z, Complex.parse("( " + re + "," + im + ")"));
Assertions.assertEquals(z, Complex.parse("(" + re + " ," + im + ")"));
Assertions.assertEquals(z, Complex.parse("(" + re + ", " + im + ")"));
Assertions.assertEquals(z, Complex.parse("(" + re + "," + im + " )"));
Assertions.assertEquals(z, Complex.parse("( " + re + " , " + im + " )"));
}
@Test
public void testCGrammar() {
final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
for (int i = 0; i < 10; i++) {
final Complex z = Complex.ofCartesian(rng.nextDouble(), rng.nextDouble());
Assertions.assertEquals(z.getReal(), z.real(), "real");
Assertions.assertEquals(z.getImaginary(), z.imag(), "imag");
}
}
@Test
public void testLog10() {
final double ln10 = Math.log(10);
final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
for (int i = 0; i < 10; i++) {
final Complex z = Complex.ofCartesian(rng.nextDouble(), rng.nextDouble());
final Complex lnz = z.log();
final Complex log10z = z.log10();
// This is prone to floating-point error so use a delta
Assertions.assertEquals(lnz.getReal() / ln10, log10z.getReal(), 1e-12, "real");
// This test should be exact
Assertions.assertEquals(lnz.getImaginary(), log10z.getImaginary(), "imag");
}
}
@Test
public void testAtanhEdgeConditions() {
// Hits the edge case when imaginary == 0 but real != 0 or 1
final Complex c = Complex.ofCartesian(2, 0).atanh();
// Answer from g++
Assertions.assertEquals(0.54930614433405489, c.getReal());
Assertions.assertEquals(1.5707963267948966, c.getImaginary());
}
@Test
public void testAtanhAssumptions() {
// Compute the same constants used by atanh
final double safeUpper = Math.sqrt(Double.MAX_VALUE) / 2;
final double safeLower = Math.sqrt(Double.MIN_NORMAL) * 2;
// Can we assume (1+x) = x when x is large
Assertions.assertEquals(safeUpper, 1 + safeUpper);
// Can we assume (1-x) = -x when x is large
Assertions.assertEquals(-safeUpper, 1 - safeUpper);
// Can we assume (y^2/x) = 0 when y is small and x is large
Assertions.assertEquals(0, safeLower * safeLower / safeUpper);
// Can we assume (1-x)^2/y + y = y when x <= 1. Try with x = 0.
Assertions.assertEquals(safeUpper, 1 / safeUpper + safeUpper);
// Can we assume (4+y^2) = 4 when y is small
Assertions.assertEquals(4, 4 + safeLower * safeLower);
// Can we assume (1-x)^2 = 1 when x is small
Assertions.assertEquals(1, (1 - safeLower) * (1 - safeLower));
// Can we assume 1 - y^2 = 1 when y is small
Assertions.assertEquals(1, 1 - safeLower * safeLower);
// Can we assume Math.log1p(4 * x / y / y) = (4 * x / y / y) when big y and small x
final double result = 4 * safeLower / safeUpper / safeUpper;
Assertions.assertEquals(result, Math.log1p(result));
Assertions.assertEquals(result, result - result * result / 2,
"Expected log1p Taylor series to be redundant");
// Can we assume if x != 1 then (x-1) is valid for multiplications.
Assertions.assertNotEquals(0, 1 - Math.nextUp(1));
Assertions.assertNotEquals(0, 1 - Math.nextDown(1));
}
}