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    *      https://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  /*
19   * This is not the original file distributed by the Apache Software Foundation
20   * It has been modified by the Hipparchus project
21   */
22  package org.hipparchus.util;
23  
24  import org.hipparchus.exception.MathRuntimeException;
25  import org.junit.jupiter.params.ParameterizedTest;
26  import org.junit.jupiter.params.provider.MethodSource;
27  
28  import java.lang.reflect.InvocationTargetException;
29  import java.lang.reflect.Method;
30  import java.lang.reflect.Modifier;
31  import java.lang.reflect.Type;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.List;
35  
36  import static org.junit.jupiter.api.Assertions.assertEquals;
37  import static org.junit.jupiter.api.Assertions.fail;
38  
39  /**
40   * Test to compare FastMath results against StrictMath results for boundary values.
41   * <p>
42   * Running all tests independently: <br>
43   * {@code mvn test -Dtest=FastMathStrictComparisonTest}<br>
44   * or just run tests against a single method (e.g. scalb):<br>
45   * {@code mvn test -Dtest=FastMathStrictComparisonTest -DargLine="-DtestMethod=scalb"}
46   */
47  public class FastMathStrictComparisonTest {
48  
49      // Values which often need special handling
50      private static final Double[] DOUBLE_SPECIAL_VALUES = {
51          -0.0, +0.0,                                         // 1,2
52          Double.NaN,                                         // 3
53          Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, // 4,5
54          -Double.MAX_VALUE, Double.MAX_VALUE,                // 6,7
55          // decreasing order of absolute value to help catch first failure
56          -Precision.EPSILON, Precision.EPSILON,              // 8,9
57          -Precision.SAFE_MIN, Precision.SAFE_MIN,            // 10,11
58          -Double.MIN_VALUE, Double.MIN_VALUE,                // 12,13
59      };
60  
61      private static final Float [] FLOAT_SPECIAL_VALUES = {
62          -0.0f, +0.0f,                                       // 1,2
63          Float.NaN,                                          // 3
64          Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY,   // 4,5
65          Float.MIN_VALUE, Float.MAX_VALUE,                   // 6,7
66          -Float.MIN_VALUE, -Float.MAX_VALUE,                 // 8,9
67      };
68  
69      private static final Object [] LONG_SPECIAL_VALUES = {
70          -1,0,1,                                             // 1,2,3
71          Long.MIN_VALUE, Long.MAX_VALUE,                     // 4,5
72      };
73  
74      private static final Object[] INT_SPECIAL_VALUES = {
75          -1,0,1,                                             // 1,2,3
76          Integer.MIN_VALUE, Integer.MAX_VALUE,               // 4,5
77      };
78  
79      private Method mathMethod;
80      private Method fastMethod;
81      private Type[] types;
82      private Object[][] valueArrays;
83  
84      public void initFastMathStrictComparisonTest(Method m, Method f, Type[] types, Object[][] data) throws Exception {
85          this.mathMethod=m;
86          this.fastMethod=f;
87          this.types=types;
88          this.valueArrays=data;
89      }
90  
91      @MethodSource("data")
92      @ParameterizedTest
93      void test1(Method m, Method f, Type[] types, Object[][] data) throws Exception {
94          initFastMathStrictComparisonTest(m, f, types, data);
95          setupMethodCall(mathMethod, fastMethod, types, valueArrays);
96      }
97      private static boolean isNumber(Double d) {
98          return !(d.isInfinite() || d.isNaN());
99      }
100 
101     private static boolean isNumber(Float f) {
102         return !(f.isInfinite() || f.isNaN());
103     }
104 
105     private static void reportFailedResults(Method mathMethod, Object[] params, Object expected, Object actual, int[] entries){
106         final String methodName = mathMethod.getName();
107         String format = null;
108         long actL=0;
109         long expL=0;
110         if (expected instanceof Double) {
111             Double exp = (Double) expected;
112             Double act = (Double) actual;
113             if (isNumber(exp) && isNumber(act) && exp != 0) { // show difference as hex
114                 actL = Double.doubleToLongBits(act);
115                 expL = Double.doubleToLongBits(exp);
116                 if (Math.abs(actL-expL)==1) {
117                     // Not 100% sure off-by-one errors are allowed everywhere, so only allow for these methods
118                     if (methodName.equals("toRadians") || methodName.equals("atan2")) {
119                         return;
120                     }
121                 }
122                 format = "%016x";
123             }
124         } else if (expected instanceof Float ){
125             Float exp = (Float) expected;
126             Float act = (Float) actual;
127             if (isNumber(exp) && isNumber(act) && exp != 0) { // show difference as hex
128                 actL = Float.floatToIntBits(act);
129                 expL = Float.floatToIntBits(exp);
130                 format = "%08x";
131             }
132         }
133         StringBuilder sb = new StringBuilder();
134         sb.append(mathMethod.getReturnType().getSimpleName());
135         sb.append(" ");
136         sb.append(methodName);
137         sb.append("(");
138         String sep = "";
139         for(Object o : params){
140             sb.append(sep);
141             sb.append(o);
142             sep=", ";
143         }
144         sb.append(") expected ");
145         if (format != null){
146             sb.append(String.format(format, expL));
147         } else {
148             sb.append(expected);
149         }
150         sb.append(" actual ");
151         if (format != null){
152             sb.append(String.format(format, actL));
153         } else {
154             sb.append(actual);
155         }
156         sb.append(" entries ");
157         sb.append(Arrays.toString(entries));
158         String message = sb.toString();
159         final boolean fatal = true;
160         if (fatal) {
161             fail(message);
162         } else {
163             System.out.println(message);
164         }
165     }
166 
167     private static void callMethods(Method mathMethod, Method fastMethod,
168             Object[] params, int[] entries) throws IllegalAccessException {
169         try {
170             Object expected;
171             try {
172                 expected = mathMethod.invoke(mathMethod, params);
173             } catch (InvocationTargetException ite) {
174                 expected = ite.getCause();
175             }
176             Object actual;
177             try {
178                 actual = fastMethod.invoke(mathMethod, params);
179             } catch (InvocationTargetException ite) {
180                 actual = ite.getCause();
181             }
182             if (expected instanceof ArithmeticException) {
183                 assertEquals(MathRuntimeException.class, actual.getClass());
184             } else  if (!expected.equals(actual)) {
185                 reportFailedResults(mathMethod, params, expected, actual, entries);
186             }
187         } catch (IllegalArgumentException e) {
188             System.out.println(mathMethod);
189             System.out.println(fastMethod);
190             System.out.print("params = ");
191             for (Object o : params) {
192                 System.out.print(" " + o);
193             }
194             System.out.println();
195             System.out.print("entries = ");
196             for (int i : entries) {
197                 System.out.print(" " + i);
198             }
199             System.out.println();
200             e.printStackTrace();
201             fail(mathMethod+" "+e);
202         }
203     }
204 
205     private static void setupMethodCall(Method mathMethod, Method fastMethod,
206                                         Type[] types, Object[][] valueArrays) throws Exception {
207         Object[] params = new Object[types.length];
208         int[] entries = new int[types.length];
209         for (int i = 0; i < params.length; ++i) {
210             for (int j = 0; j < valueArrays[i].length; ++j) {
211                 Object d = valueArrays[i][j];
212                 params[i] = d;
213                 entries[i] = j;
214             }
215         }
216         callMethods(mathMethod, fastMethod, params, entries);
217     }
218 
219     public static List<Object[]> data() throws Exception {
220         String singleMethod = System.getProperty("testMethod");
221         List<Object[]> list = new ArrayList<Object[]>();
222         for(Method mathMethod : StrictMath.class.getDeclaredMethods()) {
223             method:
224             if (Modifier.isPublic(mathMethod.getModifiers())){// Only test public methods
225                 Type []types = mathMethod.getGenericParameterTypes();
226                 if (types.length >=1) { // Only check methods with at least one parameter
227                     try {
228                         // Get the corresponding FastMath method
229                         Method fastMethod = FastMath.class.getDeclaredMethod(mathMethod.getName(), (Class[]) types);
230                         if (Modifier.isPublic(fastMethod.getModifiers())) { // It must be public too
231                             if (singleMethod != null && !fastMethod.getName().equals(singleMethod)) {
232                                 break method;
233                             }
234                             Object [][] values = new Object[types.length][];
235                             int index = 0;
236                             for(Type t : types) {
237                                 if (t.equals(double.class)){
238                                     values[index]=DOUBLE_SPECIAL_VALUES;
239                                 } else if (t.equals(float.class)) {
240                                     values[index]=FLOAT_SPECIAL_VALUES;
241                                 } else if (t.equals(long.class)) {
242                                     values[index]=LONG_SPECIAL_VALUES;
243                                 } else if (t.equals(int.class)) {
244                                     values[index]=INT_SPECIAL_VALUES;
245                                 } else {
246                                     System.out.println("Cannot handle class "+t+" for "+mathMethod);
247                                     break method;
248                                 }
249                                 index++;
250                             }
251 //                            System.out.println(fastMethod);
252                             /*
253                              * The current implementation runs each method as a separate test.
254                              * Could be amended to run each value as a separate test
255                              */
256                             list.add(new Object[]{mathMethod, fastMethod, types, values});
257 //                            setupMethodCall(mathMethod, fastMethod, params, data);
258                         } else {
259                             System.out.println("Cannot find public FastMath method corresponding to: "+mathMethod);
260                         }
261                     } catch (NoSuchMethodException e) {
262                         System.out.println("Cannot find FastMath method corresponding to: "+mathMethod);
263                     }
264                 }
265             }
266         }
267         return list;
268     }
269 }