| /* |
| * 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 org.apache.commons.numbers.complex.TestUtils.TestDataFlagOption; |
| import org.junit.jupiter.api.Assertions; |
| import org.junit.jupiter.api.Test; |
| |
| import java.util.List; |
| import java.util.function.BiFunction; |
| import java.util.function.Supplier; |
| import java.util.function.UnaryOperator; |
| |
| /** |
| * Tests the functions defined by the C.99 standard for complex numbers |
| * defined in ISO/IEC 9899, Annex G. |
| * |
| * <p>The test data is generated from a known implementation of the standard |
| * and saved to the test resources data files. |
| * |
| * @see <a href="http://www.open-std.org/JTC1/SC22/WG14/www/standards"> |
| * ISO/IEC 9899 - Programming languages - C</a> |
| */ |
| class CReferenceTest { |
| /** Positive zero bits. */ |
| private static final long POSITIVE_ZERO_DOUBLE_BITS = Double.doubleToRawLongBits(+0.0); |
| /** Negative zero bits. */ |
| private static final long NEGATIVE_ZERO_DOUBLE_BITS = Double.doubleToRawLongBits(-0.0); |
| |
| /** |
| * The maximum units of least precision (ULPs) allowed between values. |
| * This is a global setting used to override individual test settings for ULPs as follows: |
| * |
| * <ul> |
| * <li>In the normal use case this is set to zero and ignored. |
| * <li>If the sign matches the setting of the test then the larger magnitude is used. |
| * <li>If the global setting is negative and the test setting is positive then it overrides |
| * the individual test setting for reporting purposes. |
| * <li>If the global setting is positive and the test setting is negative then the test |
| * setting takes precedence. |
| * </ul> |
| * |
| * <p>During testing the difference between an expected and actual result is specified by the |
| * count of numbers in the range between them lower end exclusive and upper end inclusive. |
| * Two equal numbers have a count of 0. The next adjacent value has a count of 1. |
| * |
| * <p>If the configured ULPs is positive then the test is |
| * asserted using: {@code delta <= maxUlps}. |
| * |
| * <p>If the configured ULPs is negative the test is asserted using: |
| * {@code delta <= (|maxUlps|-1)}. This allows setting a ULPs of -1 to indicate |
| * maximum ULPs = 0 but flagging the assertion for special processing. |
| * |
| * <p>If the maximum ULPs is positive then an assertion error is raised. |
| * If negative then the error is printed to System out. This allows reporting of large |
| * deviations between the library and the reference data. |
| * |
| * <p>In a standard use-case all tests will have a configured positive maximum ULPs to |
| * pass the current test data. The global setting can be set to a negative value to allow |
| * reporting of errors larger in magnitude to the console. Setting -1 will output all |
| * differences. Setting -2 will output only those with a value between the two numbers, |
| * i.e. the numbers are not the same to floating point roundoff. |
| * |
| * <p>Setting the global maximum ULPs to negative has the second effect of loading all |
| * data that has been flagged in data files using the {@code ;} character. |
| * Otherwise this data is ignored by testing and printed to System out. |
| */ |
| private static long globalMaxUlps = 0; |
| |
| /** Set this to true to report all deviations to System out when the maximum ULPs is negative. */ |
| private static boolean reportAllDeviations = false; |
| |
| /** |
| * Assert the two numbers are equal within the provided units of least precision. |
| * The maximum count of numbers allowed between the two values is {@code maxUlps - 1}. |
| * |
| * <p>Numbers must have the same sign. Thus -0.0 and 0.0 are never equal. |
| * |
| * @param msg the failure message |
| * @param expected the expected |
| * @param actual the actual |
| * @param maxUlps the maximum units of least precision between the two values |
| */ |
| static void assertEquals(Supplier<String> msg, double expected, double actual, long maxUlps) { |
| final long e = Double.doubleToLongBits(expected); |
| final long a = Double.doubleToLongBits(actual); |
| |
| // Code adapted from Precision#equals(double, double, int) so we maintain the delta |
| // for the message. |
| |
| long delta; |
| boolean equal; |
| if (e == a) { |
| // Binary equal |
| equal = true; |
| delta = 0; |
| } else if ((a ^ e) < 0L) { |
| // Opposite signs are never equal. |
| equal = false; |
| // The difference is the count of numbers between each and zero. |
| // This may overflow but we report it using an unsigned formatter. |
| if (a < e) { |
| delta = (e - POSITIVE_ZERO_DOUBLE_BITS) + (a - NEGATIVE_ZERO_DOUBLE_BITS) + 1; |
| } else { |
| delta = (a - POSITIVE_ZERO_DOUBLE_BITS) + (e - NEGATIVE_ZERO_DOUBLE_BITS) + 1; |
| } |
| } else { |
| delta = Math.abs(e - a); |
| // Allow input of a negative maximum ULPs |
| equal = delta <= ((maxUlps < 0) ? (-maxUlps - 1) : maxUlps); |
| } |
| |
| // DEBUG: |
| if (maxUlps < 0) { |
| // CHECKSTYLE: stop Regex |
| if (!equal) { |
| System.out.printf("%s: expected <%s> != actual <%s> (ulps=%s)%n", |
| msg.get(), expected, actual, Long.toUnsignedString(delta)); |
| } else if (reportAllDeviations) { |
| System.out.printf("%s: expected <%s> == actual <%s> (ulps=0)%n", |
| msg.get(), expected, actual); |
| } |
| // CHECKSTYLE: resume Regex |
| return; |
| } |
| |
| if (!equal) { |
| Assertions.fail(String.format("%s: expected <%s> != actual <%s> (ulps=%s)", |
| msg.get(), expected, actual, Long.toUnsignedString(delta))); |
| } |
| } |
| |
| /** |
| * Assert the operation on the complex number is equal to the expected value. |
| * |
| * <p>The results are considered equal within the provided units of least |
| * precision. The maximum count of numbers allowed between the two values is |
| * {@code maxUlps - 1}. |
| * |
| * <p>Numbers must have the same sign. Thus -0.0 and 0.0 are never equal. |
| * |
| * @param c Input number. |
| * @param name the operation name |
| * @param operation the operation |
| * @param expected Expected result. |
| * @param maxUlps the maximum units of least precision between the two values |
| */ |
| static void assertComplex(Complex c, |
| String name, UnaryOperator<Complex> operation, |
| Complex expected, long maxUlps) { |
| final Complex z = operation.apply(c); |
| assertEquals(() -> c + "." + name + "(): real", expected.real(), z.real(), maxUlps); |
| assertEquals(() -> c + "." + name + "(): imaginary", expected.imag(), z.imag(), maxUlps); |
| } |
| |
| /** |
| * Assert the operation on the complex numbers is equal to the expected value. |
| * |
| * <p>The results are considered equal within the provided units of least |
| * precision. The maximum count of numbers allowed between the two values is |
| * {@code maxUlps - 1}. |
| * |
| * <p>Numbers must have the same sign. Thus -0.0 and 0.0 are never equal. |
| * |
| * @param c1 First number. |
| * @param c2 Second number. |
| * @param name the operation name |
| * @param operation the operation |
| * @param expected Expected real part. |
| * @param maxUlps the maximum units of least precision between the two values |
| */ |
| static void assertComplex(Complex c1, Complex c2, |
| String name, BiFunction<Complex, Complex, Complex> operation, |
| Complex expected, long maxUlps) { |
| final Complex z = operation.apply(c1, c2); |
| assertEquals(() -> c1 + "." + name + c2 + ": real", expected.real(), z.real(), maxUlps); |
| assertEquals(() -> c1 + "." + name + c2 + ": imaginary", expected.imag(), z.imag(), maxUlps); |
| } |
| |
| /** |
| * Assert the operation using the data loaded from test resources. |
| * |
| * @param name the operation name |
| * @param operation the operation |
| * @param maxUlps the maximum units of least precision between the two values |
| */ |
| private static void assertOperation(String name, |
| UnaryOperator<Complex> operation, long maxUlps) { |
| final List<Complex[]> data = loadTestData(name); |
| final long ulps = getTestUlps(maxUlps); |
| for (final Complex[] pair : data) { |
| assertComplex(pair[0], name, operation, pair[1], ulps); |
| } |
| } |
| |
| /** |
| * Assert the operation using the data loaded from test resources. |
| * |
| * @param name the operation name |
| * @param operation the operation |
| * @param maxUlps the maximum units of least precision between the two values |
| */ |
| private static void assertBiOperation(String name, |
| BiFunction<Complex, Complex, Complex> operation, long maxUlps) { |
| final List<Complex[]> data = loadTestData(name); |
| final long ulps = getTestUlps(maxUlps); |
| for (final Complex[] triple : data) { |
| assertComplex(triple[0], triple[1], name, operation, triple[2], ulps); |
| } |
| } |
| |
| /** |
| * Assert the operation using the data loaded from test resources. |
| * |
| * @param testData Test data resource name. |
| * @return the list |
| */ |
| private static List<Complex[]> loadTestData(String name) { |
| final String testData = "data/" + name + ".txt"; |
| final TestDataFlagOption option = globalMaxUlps < 1 ? |
| TestDataFlagOption.LOAD : TestDataFlagOption.IGNORE; |
| return TestUtils.loadTestData(testData, option, |
| // CHECKSTYLE: stop Regex |
| s -> System.out.println(name + " IGNORED: " + s)); |
| // CHECKSTYLE: resume Regex |
| } |
| |
| /** |
| * Gets the test ulps. This uses the input value of the global setting if that is greater |
| * in magnitude. |
| * |
| * @param ulps the ulps |
| * @return the test ulps |
| */ |
| private static long getTestUlps(long ulps) { |
| // If sign matches use the larger magnitude. |
| // xor the sign bytes will be negative if the sign does not match |
| if ((globalMaxUlps ^ ulps) >= 0) { |
| final long max = Math.max(Math.abs(globalMaxUlps), Math.abs(ulps)); |
| // restore sign |
| return ulps < 0 ? -max : max; |
| } |
| // If the global setting is negative and the test setting is positive then it overrides |
| // the individual test setting for reporting purposes. |
| if (globalMaxUlps < 0) { |
| return globalMaxUlps; |
| } |
| // If the global setting is positive and the test setting is negative then the test |
| // setting takes precedence. |
| return ulps; |
| } |
| |
| @Test |
| void testAcos() { |
| assertOperation("acos", Complex::acos, 2); |
| } |
| |
| @Test |
| void testAcosh() { |
| assertOperation("acosh", Complex::acosh, 2); |
| } |
| |
| @Test |
| void testAsinh() { |
| // Odd function: negative real cases defined by positive real cases |
| assertOperation("asinh", Complex::asinh, 3); |
| } |
| |
| @Test |
| void testAtanh() { |
| // Odd function: negative real cases defined by positive real cases |
| assertOperation("atanh", Complex::atanh, 1); |
| } |
| |
| @Test |
| void testCosh() { |
| // Even function: negative real cases defined by positive real cases |
| assertOperation("cosh", Complex::cosh, 2); |
| } |
| |
| @Test |
| void testSinh() { |
| // Odd function: negative real cases defined by positive real cases |
| assertOperation("sinh", Complex::sinh, 2); |
| } |
| |
| @Test |
| void testTanh() { |
| // Odd function: negative real cases defined by positive real cases |
| assertOperation("tanh", Complex::tanh, 2); |
| } |
| |
| @Test |
| void testExp() { |
| assertOperation("exp", Complex::exp, 2); |
| } |
| |
| @Test |
| void testLog() { |
| assertOperation("log", Complex::log, 1); |
| } |
| |
| @Test |
| void testSqrt() { |
| assertOperation("sqrt", Complex::sqrt, 1); |
| } |
| |
| @Test |
| void testMultiply() { |
| assertBiOperation("multiply", Complex::multiply, 0); |
| } |
| |
| @Test |
| void testDivide() { |
| assertBiOperation("divide", Complex::divide, 7); |
| } |
| |
| @Test |
| void testPowComplex() { |
| assertBiOperation("pow", Complex::pow, 9); |
| } |
| } |