This is an automated email from the ASF dual-hosted git repository. aherbert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-numbers.git
commit b6be43eb887a76f95fe2527f6936f6fc382e6d0c Author: aherbert <[email protected]> AuthorDate: Wed Apr 1 11:36:39 2020 +0100 Update ComplexTest to match the method order of Complex. Added note about the incomplete testing of ISO C99 math functions. These are covered in other test classes. --- .../commons/numbers/complex/ComplexTest.java | 1693 ++++++++++---------- 1 file changed, 860 insertions(+), 833 deletions(-) diff --git a/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexTest.java b/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexTest.java index 57b1228..1dcf513 100644 --- a/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexTest.java +++ b/commons-numbers-complex/src/test/java/org/apache/commons/numbers/complex/ComplexTest.java @@ -32,6 +32,16 @@ import org.junit.jupiter.api.Test; /** * Tests for {@link Complex}. + * + * <p>Note: The ISO C99 math functions are not fully tested in this class. See also: + * + * <ul> + * <li>{@link CStandardTest} for a test of the ISO C99 standards including special case handling. + * <li>{@link CReferenceTest} for a test of the output using standard finite value against an + * ISO C99 compliant reference implementation. + * <li>{@link ComplexEdgeCaseTest} for a test of extreme edge case finite values for real and/or + * imaginary parts that can create intermediate overflow or underflow. + * </ul> */ public class ComplexTest { @@ -84,6 +94,38 @@ public class ComplexTest { } @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 testCartesianConstructor() { final Complex z = Complex.ofCartesian(3.0, 4.0); Assertions.assertEquals(3.0, z.getReal()); @@ -126,7 +168,8 @@ public class ComplexTest { @Test public void testPolarConstructorAbsArg() { - // The test should work with any seed but use a fixed seed to avoid build instability. + // 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(); @@ -147,6 +190,214 @@ public class ComplexTest { Assertions.assertEquals(Math.sin(x), z.getImaginary()); } + /** + * Test parse and toString are compatible. + */ + @Test + public void testParseAndToString() { + 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 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()); + Assertions.assertEquals(inf, Complex.ofCartesian(inf, 3.0).abs()); + Assertions.assertEquals(inf, Complex.ofCartesian(-inf, 3.0).abs()); + Assertions.assertEquals(inf, Complex.ofCartesian(3.0, inf).abs()); + Assertions.assertEquals(inf, Complex.ofCartesian(3.0, -inf).abs()); + } + + /** + * Test standard values + */ + @Test + public void testArg() { + Complex z = Complex.ofCartesian(1, 0); + assertArgument(0.0, z, 1.0e-12); + + z = Complex.ofCartesian(1, 1); + assertArgument(Math.PI / 4, z, 1.0e-12); + + z = Complex.ofCartesian(0, 1); + assertArgument(Math.PI / 2, z, 1.0e-12); + + z = Complex.ofCartesian(-1, 1); + assertArgument(3 * Math.PI / 4, z, 1.0e-12); + + z = Complex.ofCartesian(-1, 0); + assertArgument(Math.PI, z, 1.0e-12); + + z = Complex.ofCartesian(-1, -1); + assertArgument(-3 * Math.PI / 4, z, 1.0e-12); + + z = Complex.ofCartesian(0, -1); + assertArgument(-Math.PI / 2, z, 1.0e-12); + + z = Complex.ofCartesian(1, -1); + assertArgument(-Math.PI / 4, z, 1.0e-12); + } + + /** + * Verify atan2-style handling of infinite parts + */ + @Test + public void testArgInf() { + assertArgument(Math.PI / 4, infInf, 1.0e-12); + assertArgument(Math.PI / 2, oneInf, 1.0e-12); + assertArgument(0.0, infOne, 1.0e-12); + assertArgument(Math.PI / 2, zeroInf, 1.0e-12); + assertArgument(0.0, infZero, 1.0e-12); + assertArgument(Math.PI, negInfOne, 1.0e-12); + assertArgument(-3.0 * Math.PI / 4, negInfNegInf, 1.0e-12); + assertArgument(-Math.PI / 2, oneNegInf, 1.0e-12); + } + + /** + * Verify that either part NaN results in NaN + */ + @Test + public void testArgNaN() { + assertArgument(Double.NaN, nanZero, 0); + assertArgument(Double.NaN, zeroNan, 0); + assertArgument(Double.NaN, NAN, 0); + } + + private static void assertArgument(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 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 all number types: isNaN, isInfinite, isFinite. + */ @Test public void testNumberType() { assertNumberType(0, 0, NumberType.FINITE); @@ -206,6 +457,42 @@ public class ComplexTest { } @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 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 testProj() { final Complex z = Complex.ofCartesian(3.0, 4.0); Assertions.assertSame(z, z.proj()); @@ -222,48 +509,6 @@ public class ComplexTest { } @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()); - Assertions.assertEquals(inf, Complex.ofCartesian(inf, 3.0).abs()); - Assertions.assertEquals(inf, Complex.ofCartesian(-inf, 3.0).abs()); - Assertions.assertEquals(inf, Complex.ofCartesian(3.0, inf).abs()); - Assertions.assertEquals(inf, Complex.ofCartesian(3.0, -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); @@ -377,198 +622,203 @@ public class ComplexTest { } @Test - public void testConjugate() { + public void testSubtract() { 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()); + 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 testConjugateNaN() { - final Complex z = NAN.conj(); - Assertions.assertTrue(z.isNaN()); - } + 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()); - @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()); + z = y.subtract(y); + Assertions.assertEquals(nan, z.getReal()); + Assertions.assertEquals(0.0, z.getImaginary()); } @Test - public void testDivide() { + public void testSubtractReal() { 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()); + 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 testDivideZero() { + public void testSubtractRealNaN() { 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); + 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 testDivideNaN() { + public void testSubtractRealInf() { final Complex x = Complex.ofCartesian(3.0, 4.0); - final Complex z = x.divide(NAN); - Assertions.assertTrue(z.isNaN()); + 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 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())); + 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 testDivideReal() { + public void testSubtractImaginary() { 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()); + 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.divide(ofReal(-y))); + Assertions.assertEquals(z, x.subtract(ofImaginary(y))); } @Test - public void testDivideRealNaN() { + public void testSubtractImaginaryNaN() { final Complex x = Complex.ofCartesian(3.0, 4.0); final double y = nan; - final Complex z = x.divide(y); - Assertions.assertEquals(nan, z.getReal()); + final Complex z = x.subtractImaginary(y); + Assertions.assertEquals(3.0, z.getReal()); Assertions.assertEquals(nan, z.getImaginary()); // Equivalent - Assertions.assertEquals(z, x.divide(ofReal(y))); + Assertions.assertEquals(z, x.subtract(ofImaginary(y))); } @Test - public void testDivideRealInf() { + public void testSubtractImaginaryInf() { 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()); + final Complex z = x.subtractImaginary(y); + Assertions.assertEquals(3.0, z.getReal()); + Assertions.assertEquals(-inf, z.getImaginary()); // Equivalent - Assertions.assertEquals(z, x.divide(ofReal(y))); + Assertions.assertEquals(z, x.subtract(ofImaginary(y))); + } - z = x.divide(-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(-0.0, z.getImaginary()); + Assertions.assertEquals(-1.0, z.getImaginary()); // Equivalent - Assertions.assertEquals(z, x.divide(ofReal(-y))); + // Sign-preservation is not a problem: -0.0 - 0.0 == -0.0 + Assertions.assertEquals(z, x.subtract(ofImaginary(y))); } @Test - public void testDivideRealZero() { + public void testSubtractFromReal() { 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()); + 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, x.divide(ofReal(y))); + Assertions.assertEquals(z, ofReal(y).subtract(x)); + } - z = x.divide(-y); - Assertions.assertEquals(-inf, z.getReal()); - Assertions.assertEquals(-inf, z.getImaginary()); + @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, x.divide(ofReal(-y))); + Assertions.assertEquals(z, ofReal(y).subtract(x)); } @Test - public void testDivideImaginary() { + public void testSubtractFromRealInf() { 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()); + 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, x.divide(ofImaginary(y))); + Assertions.assertEquals(z, ofReal(y).subtract(x)); + } - z = x.divideImaginary(-y); - Assertions.assertEquals(-2.0, z.getReal()); - Assertions.assertEquals(1.5, z.getImaginary()); + @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, x.divide(ofImaginary(-y))); + Assertions.assertEquals(z, ofImaginary(y).subtract(x)); } @Test - public void testDivideImaginaryNaN() { + public void testSubtractFromImaginaryNaN() { final Complex x = Complex.ofCartesian(3.0, 4.0); final double y = nan; - final Complex z = x.divideImaginary(y); - Assertions.assertEquals(nan, z.getReal()); + final Complex z = x.subtractFromImaginary(y); + Assertions.assertEquals(-3.0, z.getReal()); Assertions.assertEquals(nan, z.getImaginary()); // Equivalent - Assertions.assertEquals(z, x.divide(ofImaginary(y))); + Assertions.assertEquals(z, ofImaginary(y).subtract(x)); } @Test - public void testDivideImaginaryInf() { + public void testSubtractFromImaginaryInf() { 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()); + final Complex z = x.subtractFromImaginary(y); + Assertions.assertEquals(-3.0, z.getReal()); + Assertions.assertEquals(inf, 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))); - } + Assertions.assertEquals(z, ofImaginary(y).subtract(x)); + } @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()); + 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 @@ -760,7 +1010,7 @@ public class ComplexTest { } @Test - public void testZeroMultiplyI() { + public void testMultiplyZeroByI() { final double[] zeros = {-0.0, 0.0}; for (final double a : zeros) { for (final double b : zeros) { @@ -785,7 +1035,7 @@ public class ComplexTest { } @Test - public void testZeroMultiplyNegativeI() { + public void testMultiplyZeroByNegativeI() { // 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(); @@ -821,8 +1071,181 @@ public class ComplexTest { } } + @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()); + } + /** - * Arithmetic test using combinations of +/- x for real, imaginary and and the double + * Arithmetic test using combinations of +/- x for real, imaginary and the double * argument for add, subtract, subtractFrom, multiply and divide, where x is zero or * non-zero. * @@ -939,7 +1362,7 @@ public class ComplexTest { * by zero using a Complex then multiplied by I. */ @Test - public void testDivideImaginaryArithmetic() { + public void testSignedDivideImaginaryArithmetic() { // Cases for divide by non-zero: // 2: (-0.0,+x) / -y // 4: (+x,+/-0.0) / -/+y @@ -991,497 +1414,37 @@ public class ComplexTest { } @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()); + 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 testNegateNaN() { - final Complex z = NAN.negate(); - Assertions.assertTrue(z.isNaN()); + 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 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 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()); + 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 @@ -1746,191 +1709,245 @@ public class ComplexTest { 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())); + Assertions.assertTrue(Double.isNaN(c.getImaginary())); + } } @Test - public void testParseNull() { - Assertions.assertThrows(NullPointerException.class, () -> Complex.parse(null)); + 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 - public void testParseEmpty() { - Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("")); - Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse(" ")); + public void testEqualsWithNull() { + final Complex x = Complex.ofCartesian(3.0, 4.0); + Assertions.assertFalse(x.equals(null)); } @Test - public void testParseWrongStart() { - Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("1.0,2.0)")); - Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("[1.0,2.0)")); + public void testEqualsWithAnotherClass() { + final Complex x = Complex.ofCartesian(3.0, 4.0); + Assertions.assertFalse(x.equals(new Object())); } @Test - public void testParseWrongEnd() { - Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,2.0")); - Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,2.0]")); + public void testEqualsWithSameObject() { + final Complex x = Complex.ofCartesian(3.0, 4.0); + Assertions.assertEquals(x, x); } @Test - public void testParseWrongSeparator() { - Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0 2.0)")); - Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0:2.0)")); + 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 testParseSeparatorOutsideStartAndEnd() { - Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,2.0),")); - Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse(",(1.0,2.0)")); + 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 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)")); + 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 testParseInvalidRe() { - Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(I.0,2.0)")); + 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 testParseInvalidIm() { - Assertions.assertThrows(NumberFormatException.class, () -> Complex.parse("(1.0,2.G)")); + 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 - 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 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 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"); + 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 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"); + 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 @@ -1962,11 +1979,11 @@ public class ComplexTest { 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 + // 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"); + 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)); @@ -2038,9 +2055,9 @@ public class ComplexTest { } /** - * Test the abs and sqrt functions are consistent. The definition of sqrt uses abs - * and the result should be computed using the same representation of the - * complex number's magnitude (abs). If the sqrt function uses a simple representation + * Test the abs and sqrt functions are consistent. The definition of sqrt uses abs and + * the result should be computed using the same representation of the complex number's + * magnitude (abs). If the sqrt function uses a simple representation * {@code sqrt(x^2 + y^2)} then this may have a 1 ulp or more difference from the high * accuracy result computed by abs. This will propagate to create differences in sqrt. * @@ -2051,24 +2068,28 @@ public class ComplexTest { public void testAbsVsSqrt() { final UniformRandomProvider rng = RandomSource.create(RandomSource.XO_RO_SHI_RO_128_PP); // Note: All methods implement scaling to ensure the magnitude can be computed. - // Try very large or small numbers that will over/underflow to test that the scaling + // Try very large or small numbers that will over/underflow to test that the + // scaling // is consistent. Note that: // - sqrt will reduce the size of the real and imaginary - // components when |z|>1 and increase them when |z|<1. + // components when |z|>1 and increase them when |z|<1. - // Each sample fails approximately 3% of the time if using a standard x^2+y^2 in sqrt() + // Each sample fails approximately 3% of the time if using a standard x^2+y^2 in + // sqrt() // and high accuracy representation in abs(). // Use 1000 samples to ensure the behavior is OK. - // Do not use data which will over/underflow so we can use a simple computation in the test - assertAbsVsSqrt(1000, () -> Complex.ofCartesian(createFixedExponentNumber(rng, 1000), - createFixedExponentNumber(rng, 1000))); - assertAbsVsSqrt(1000, () -> Complex.ofCartesian(createFixedExponentNumber(rng, -1000), - createFixedExponentNumber(rng, -1000))); + // Do not use data which will over/underflow so we can use a simple computation in + // the test + assertAbsVsSqrt(1000, + () -> Complex.ofCartesian(createFixedExponentNumber(rng, 1000), createFixedExponentNumber(rng, 1000))); + assertAbsVsSqrt(1000, + () -> Complex.ofCartesian(createFixedExponentNumber(rng, -1000), createFixedExponentNumber(rng, -1000))); } private static void assertAbsVsSqrt(int samples, Supplier<Complex> supplier) { // Note: All methods implement scaling to ensure the magnitude can be computed. - // Try very large or small numbers that will over/underflow to test that the scaling + // Try very large or small numbers that will over/underflow to test that the + // scaling // is consistent. for (int i = 0; i < samples; i++) { final Complex z = supplier.get(); @@ -2080,8 +2101,9 @@ public class ComplexTest { // sqrt(x + iy) // t = sqrt( 2 (|x| + |x + iy|) ) // if x >= 0: (t/2, y/t) - // else : (|y| / t, t/2 * sgn(y)) - // Note this is not the definitional polar computation using absolute and argument: + // else : (|y| / t, t/2 * sgn(y)) + // Note this is not the definitional polar computation using absolute and + // argument: // real = sqrt(|z|) * cos(0.5 * arg(z)) // imag = sqrt(|z|) * sin(0.5 * arg(z)) final Complex c = z.sqrt(); @@ -2097,9 +2119,9 @@ public class ComplexTest { } /** - * Test the abs and log functions are consistent. The definition of log uses abs - * and the result should be computed using the same representation of the - * complex number's magnitude (abs). If the log function uses a simple representation + * Test the abs and log functions are consistent. The definition of log uses abs and + * the result should be computed using the same representation of the complex number's + * magnitude (abs). If the log function uses a simple representation * {@code sqrt(x^2 + y^2)} then this may have a 1 ulp or more difference from the high * accuracy result computed by abs. This will propagate to create differences in log. * @@ -2110,25 +2132,30 @@ public class ComplexTest { public void testAbsVsLog() { final UniformRandomProvider rng = RandomSource.create(RandomSource.XO_RO_SHI_RO_128_PP); // Note: All methods implement scaling to ensure the magnitude can be computed. - // Try very large or small numbers that will over/underflow to test that the scaling + // Try very large or small numbers that will over/underflow to test that the + // scaling // is consistent. Note that: // - log will set the real component using log(|z|). This will massively reduce - // the magnitude when |z| >> 1. Highest accuracy will be when |z| is as large - // as possible before computing the log. + // the magnitude when |z| >> 1. Highest accuracy will be when |z| is as large + // as possible before computing the log. - // No test around |z| == 1 as a high accuracy computation is required: Math.log1p(x*x+y*y-1) + // No test around |z| == 1 as a high accuracy computation is required: + // Math.log1p(x*x+y*y-1) - // Each sample fails approximately 25% of the time if using a standard x^2+y^2 in log() - // and high accuracy representation in abs(). Use 100 samples to ensure the behavior is OK. - assertAbsVsLog(100, () -> Complex.ofCartesian(createFixedExponentNumber(rng, 1022), - createFixedExponentNumber(rng, 1022))); - assertAbsVsLog(100, () -> Complex.ofCartesian(createFixedExponentNumber(rng, -1022), - createFixedExponentNumber(rng, -1022))); + // Each sample fails approximately 25% of the time if using a standard x^2+y^2 in + // log() + // and high accuracy representation in abs(). Use 100 samples to ensure the + // behavior is OK. + assertAbsVsLog(100, + () -> Complex.ofCartesian(createFixedExponentNumber(rng, 1022), createFixedExponentNumber(rng, 1022))); + assertAbsVsLog(100, + () -> Complex.ofCartesian(createFixedExponentNumber(rng, -1022), createFixedExponentNumber(rng, -1022))); } private static void assertAbsVsLog(int samples, Supplier<Complex> supplier) { // Note: All methods implement scaling to ensure the magnitude can be computed. - // Try very large or small numbers that will over/underflow to test that the scaling + // Try very large or small numbers that will over/underflow to test that the + // scaling // is consistent. for (int i = 0; i < samples; i++) { final Complex z = supplier.get();
