View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.numbers.complex;
19  
20  import org.apache.commons.numbers.complex.TestUtils.TestDataFlagOption;
21  import org.junit.jupiter.api.Assertions;
22  import org.junit.jupiter.api.Test;
23  
24  import java.util.List;
25  import java.util.function.BiFunction;
26  import java.util.function.Supplier;
27  import java.util.function.UnaryOperator;
28  
29  /**
30   * Tests the functions defined by the C.99 standard for complex numbers
31   * defined in ISO/IEC 9899, Annex G.
32   *
33   * <p>The test data is generated from a known implementation of the standard
34   * and saved to the test resources data files.
35   *
36   * @see <a href="http://www.open-std.org/JTC1/SC22/WG14/www/standards">
37   *    ISO/IEC 9899 - Programming languages - C</a>
38   */
39  class CReferenceTest {
40      /** Positive zero bits. */
41      private static final long POSITIVE_ZERO_DOUBLE_BITS = Double.doubleToRawLongBits(+0.0);
42      /** Negative zero bits. */
43      private static final long NEGATIVE_ZERO_DOUBLE_BITS = Double.doubleToRawLongBits(-0.0);
44  
45      /**
46       * The maximum units of least precision (ULPs) allowed between values.
47       * This is a global setting used to override individual test settings for ULPs as follows:
48       *
49       * <ul>
50       * <li>In the normal use case this is set to zero and ignored.
51       * <li>If the sign matches the setting of the test then the larger magnitude is used.
52       * <li>If the global setting is negative and the test setting is positive then it overrides
53       * the individual test setting for reporting purposes.
54       * <li>If the global setting is positive and the test setting is negative then the test
55       * setting takes precedence.
56       * </ul>
57       *
58       * <p>During testing the difference between an expected and actual result is specified by the
59       * count of numbers in the range between them lower end exclusive and upper end inclusive.
60       * Two equal numbers have a count of 0. The next adjacent value has a count of 1.
61       *
62       * <p>If the configured ULPs is positive then the test is
63       * asserted using: {@code delta <= maxUlps}.
64       *
65       * <p>If the configured ULPs is negative the test is asserted using:
66       * {@code delta <= (|maxUlps|-1)}. This allows setting a ULPs of -1 to indicate
67       * maximum ULPs = 0 but flagging the assertion for special processing.
68       *
69       * <p>If the maximum ULPs is positive then an assertion error is raised.
70       * If negative then the error is printed to System out. This allows reporting of large
71       * deviations between the library and the reference data.
72       *
73       * <p>In a standard use-case all tests will have a configured positive maximum ULPs to
74       * pass the current test data. The global setting can be set to a negative value to allow
75       * reporting of errors larger in magnitude to the console. Setting -1 will output all
76       * differences. Setting -2 will output only those with a value between the two numbers,
77       * i.e. the numbers are not the same to floating point roundoff.
78       *
79       * <p>Setting the global maximum ULPs to negative has the second effect of loading all
80       * data that has been flagged in data files using the {@code ;} character.
81       * Otherwise this data is ignored by testing and printed to System out.
82       */
83      private static long globalMaxUlps = 0;
84  
85      /** Set this to true to report all deviations to System out when the maximum ULPs is negative. */
86      private static boolean reportAllDeviations = false;
87  
88      /**
89       * Assert the two numbers are equal within the provided units of least precision.
90       * The maximum count of numbers allowed between the two values is {@code maxUlps - 1}.
91       *
92       * <p>Numbers must have the same sign. Thus -0.0 and 0.0 are never equal.
93       *
94       * @param msg the failure message
95       * @param expected the expected
96       * @param actual the actual
97       * @param maxUlps the maximum units of least precision between the two values
98       */
99      static void assertEquals(Supplier<String> msg, double expected, double actual, long maxUlps) {
100         final long e = Double.doubleToLongBits(expected);
101         final long a = Double.doubleToLongBits(actual);
102 
103         // Code adapted from Precision#equals(double, double, int) so we maintain the delta
104         // for the message.
105 
106         long delta;
107         boolean equal;
108         if (e == a) {
109             // Binary equal
110             equal = true;
111             delta = 0;
112         } else if ((a ^ e) < 0L) {
113             // Opposite signs are never equal.
114             equal = false;
115             // The difference is the count of numbers between each and zero.
116             // This may overflow but we report it using an unsigned formatter.
117             if (a < e) {
118                 delta = (e - POSITIVE_ZERO_DOUBLE_BITS) + (a - NEGATIVE_ZERO_DOUBLE_BITS) + 1;
119             } else {
120                 delta = (a - POSITIVE_ZERO_DOUBLE_BITS) + (e - NEGATIVE_ZERO_DOUBLE_BITS) + 1;
121             }
122         } else {
123             delta = Math.abs(e - a);
124             // Allow input of a negative maximum ULPs
125             equal = delta <= ((maxUlps < 0) ? (-maxUlps - 1) : maxUlps);
126         }
127 
128         // DEBUG:
129         if (maxUlps < 0) {
130             // CHECKSTYLE: stop Regex
131             if (!equal) {
132                 System.out.printf("%s: expected <%s> != actual <%s> (ulps=%s)%n",
133                         msg.get(), expected, actual, Long.toUnsignedString(delta));
134             } else if (reportAllDeviations) {
135                 System.out.printf("%s: expected <%s> == actual <%s> (ulps=0)%n",
136                     msg.get(), expected, actual);
137             }
138             // CHECKSTYLE: resume Regex
139             return;
140         }
141 
142         if (!equal) {
143             Assertions.fail(String.format("%s: expected <%s> != actual <%s> (ulps=%s)",
144                     msg.get(), expected, actual, Long.toUnsignedString(delta)));
145         }
146     }
147 
148     /**
149      * Assert the operation on the complex number is equal to the expected value.
150      *
151      * <p>The results are considered equal within the provided units of least
152      * precision. The maximum count of numbers allowed between the two values is
153      * {@code maxUlps - 1}.
154      *
155      * <p>Numbers must have the same sign. Thus -0.0 and 0.0 are never equal.
156      *
157      * @param c Input number.
158      * @param name the operation name
159      * @param operation the operation
160      * @param expected Expected result.
161      * @param maxUlps the maximum units of least precision between the two values
162      */
163     static void assertComplex(Complex c,
164             String name, UnaryOperator<Complex> operation,
165             Complex expected, long maxUlps) {
166         final Complex z = operation.apply(c);
167         assertEquals(() -> c + "." + name + "(): real", expected.real(), z.real(), maxUlps);
168         assertEquals(() -> c + "." + name + "(): imaginary", expected.imag(), z.imag(), maxUlps);
169     }
170 
171     /**
172      * Assert the operation on the complex numbers is equal to the expected value.
173      *
174      * <p>The results are considered equal within the provided units of least
175      * precision. The maximum count of numbers allowed between the two values is
176      * {@code maxUlps - 1}.
177      *
178      * <p>Numbers must have the same sign. Thus -0.0 and 0.0 are never equal.
179      *
180      * @param c1 First number.
181      * @param c2 Second number.
182      * @param name the operation name
183      * @param operation the operation
184      * @param expected Expected real part.
185      * @param maxUlps the maximum units of least precision between the two values
186      */
187     static void assertComplex(Complex c1, Complex c2,
188             String name, BiFunction<Complex, Complex, Complex> operation,
189             Complex expected, long maxUlps) {
190         final Complex z = operation.apply(c1, c2);
191         assertEquals(() -> c1 + "." + name + c2 + ": real", expected.real(), z.real(), maxUlps);
192         assertEquals(() -> c1 + "." + name + c2 + ": imaginary", expected.imag(), z.imag(), maxUlps);
193     }
194 
195     /**
196      * Assert the operation using the data loaded from test resources.
197      *
198      * @param name the operation name
199      * @param operation the operation
200      * @param maxUlps the maximum units of least precision between the two values
201      */
202     private static void assertOperation(String name,
203             UnaryOperator<Complex> operation, long maxUlps) {
204         final List<Complex[]> data = loadTestData(name);
205         final long ulps = getTestUlps(maxUlps);
206         for (final Complex[] pair : data) {
207             assertComplex(pair[0], name, operation, pair[1], ulps);
208         }
209     }
210 
211     /**
212      * Assert the operation using the data loaded from test resources.
213      *
214      * @param name the operation name
215      * @param operation the operation
216      * @param maxUlps the maximum units of least precision between the two values
217      */
218     private static void assertBiOperation(String name,
219             BiFunction<Complex, Complex, Complex> operation, long maxUlps) {
220         final List<Complex[]> data = loadTestData(name);
221         final long ulps = getTestUlps(maxUlps);
222         for (final Complex[] triple : data) {
223             assertComplex(triple[0], triple[1], name, operation, triple[2], ulps);
224         }
225     }
226 
227     /**
228      * Assert the operation using the data loaded from test resources.
229      *
230      * @param testData Test data resource name.
231      * @return the list
232      */
233     private static List<Complex[]> loadTestData(String name) {
234         final String testData = "data/" + name + ".txt";
235         final TestDataFlagOption option = globalMaxUlps < 1 ?
236             TestDataFlagOption.LOAD : TestDataFlagOption.IGNORE;
237         return TestUtils.loadTestData(testData, option,
238             // CHECKSTYLE: stop Regex
239             s -> System.out.println(name + " IGNORED: " + s));
240             // CHECKSTYLE: resume Regex
241     }
242 
243     /**
244      * Gets the test ulps. This uses the input value of the global setting if that is greater
245      * in magnitude.
246      *
247      * @param ulps the ulps
248      * @return the test ulps
249      */
250     private static long getTestUlps(long ulps) {
251         // If sign matches use the larger magnitude.
252         // xor the sign bytes will be negative if the sign does not match
253         if ((globalMaxUlps ^ ulps) >= 0) {
254             final long max = Math.max(Math.abs(globalMaxUlps), Math.abs(ulps));
255             // restore sign
256             return ulps < 0 ? -max : max;
257         }
258         // If the global setting is negative and the test setting is positive then it overrides
259         // the individual test setting for reporting purposes.
260         if (globalMaxUlps < 0) {
261             return globalMaxUlps;
262         }
263         // If the global setting is positive and the test setting is negative then the test
264         // setting takes precedence.
265         return ulps;
266     }
267 
268     @Test
269     void testAcos() {
270         assertOperation("acos", Complex::acos, 2);
271     }
272 
273     @Test
274     void testAcosh() {
275         assertOperation("acosh", Complex::acosh, 2);
276     }
277 
278     @Test
279     void testAsinh() {
280         // Odd function: negative real cases defined by positive real cases
281         assertOperation("asinh", Complex::asinh, 3);
282     }
283 
284     @Test
285     void testAtanh() {
286         // Odd function: negative real cases defined by positive real cases
287         assertOperation("atanh", Complex::atanh, 1);
288     }
289 
290     @Test
291     void testCosh() {
292         // Even function: negative real cases defined by positive real cases
293         assertOperation("cosh", Complex::cosh, 2);
294     }
295 
296     @Test
297     void testSinh() {
298         // Odd function: negative real cases defined by positive real cases
299         assertOperation("sinh", Complex::sinh, 2);
300     }
301 
302     @Test
303     void testTanh() {
304         // Odd function: negative real cases defined by positive real cases
305         assertOperation("tanh", Complex::tanh, 2);
306     }
307 
308     @Test
309     void testExp() {
310         assertOperation("exp", Complex::exp, 2);
311     }
312 
313     @Test
314     void testLog() {
315         assertOperation("log", Complex::log, 1);
316     }
317 
318     @Test
319     void testSqrt() {
320         assertOperation("sqrt", Complex::sqrt, 1);
321     }
322 
323     @Test
324     void testMultiply() {
325         assertBiOperation("multiply", Complex::multiply, 0);
326     }
327 
328     @Test
329     void testDivide() {
330         assertBiOperation("divide", Complex::divide, 7);
331     }
332 
333     @Test
334     void testPowComplex() {
335         assertBiOperation("pow", Complex::pow, 9);
336     }
337 }