| /* |
| * 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 testPolarConstructorAbsArg() { |
| // The test should work with any seed but use a fixed seed to avoid build instability. |
| final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64, 678678638L); |
| for (int i = 0; i < 10; i++) { |
| final double rho = rng.nextDouble(); |
| // Range (pi, pi]: lower exclusive, upper inclusive |
| final double theta = pi - rng.nextDouble() * 2 * pi; |
| final Complex z = Complex.ofPolar(rho, theta); |
| // Match within 1 ULP |
| Assertions.assertEquals(rho, z.abs(), Math.ulp(rho)); |
| Assertions.assertEquals(theta, z.arg(), Math.ulp(theta)); |
| } |
| } |
| |
| @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() * 2, rng.nextDouble() * 2); |
| 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)); |
| } |
| } |