blob: 322c94622ac5456c9a73eb1a1b6cf98ed1c40831 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.numbers.complex;
import 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>
*/
public 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);
}
}