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 java.lang.reflect.InvocationTargetException;
25  import java.lang.reflect.Method;
26  import java.lang.reflect.Modifier;
27  import java.lang.reflect.Type;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.List;
31  
32  import org.hipparchus.exception.MathRuntimeException;
33  import org.junit.Assert;
34  import org.junit.Test;
35  import org.junit.runner.RunWith;
36  import org.junit.runners.Parameterized;
37  import org.junit.runners.Parameterized.Parameters;
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  @RunWith(Parameterized.class)
48  public class FastMathStrictComparisonTest {
49  
50      // Values which often need special handling
51      private static final Double[] DOUBLE_SPECIAL_VALUES = {
52          -0.0, +0.0,                                         // 1,2
53          Double.NaN,                                         // 3
54          Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, // 4,5
55          -Double.MAX_VALUE, Double.MAX_VALUE,                // 6,7
56          // decreasing order of absolute value to help catch first failure
57          -Precision.EPSILON, Precision.EPSILON,              // 8,9
58          -Precision.SAFE_MIN, Precision.SAFE_MIN,            // 10,11
59          -Double.MIN_VALUE, Double.MIN_VALUE,                // 12,13
60      };
61  
62      private static final Float [] FLOAT_SPECIAL_VALUES = {
63          -0.0f, +0.0f,                                       // 1,2
64          Float.NaN,                                          // 3
65          Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY,   // 4,5
66          Float.MIN_VALUE, Float.MAX_VALUE,                   // 6,7
67          -Float.MIN_VALUE, -Float.MAX_VALUE,                 // 8,9
68      };
69  
70      private static final Object [] LONG_SPECIAL_VALUES = {
71          -1,0,1,                                             // 1,2,3
72          Long.MIN_VALUE, Long.MAX_VALUE,                     // 4,5
73      };
74  
75      private static final Object[] INT_SPECIAL_VALUES = {
76          -1,0,1,                                             // 1,2,3
77          Integer.MIN_VALUE, Integer.MAX_VALUE,               // 4,5
78      };
79  
80      private final Method mathMethod;
81      private final Method fastMethod;
82      private final Type[] types;
83      private final Object[][] valueArrays;
84  
85      public FastMathStrictComparisonTest(Method m, Method f, Type[] types, Object[][] data) throws Exception{
86          this.mathMethod=m;
87          this.fastMethod=f;
88          this.types=types;
89          this.valueArrays=data;
90      }
91  
92      @Test
93      public void test1() throws Exception{
94          setupMethodCall(mathMethod, fastMethod, types, valueArrays);
95      }
96      private static boolean isNumber(Double d) {
97          return !(d.isInfinite() || d.isNaN());
98      }
99  
100     private static boolean isNumber(Float f) {
101         return !(f.isInfinite() || f.isNaN());
102     }
103 
104     private static void reportFailedResults(Method mathMethod, Object[] params, Object expected, Object actual, int[] entries){
105         final String methodName = mathMethod.getName();
106         String format = null;
107         long actL=0;
108         long expL=0;
109         if (expected instanceof Double) {
110             Double exp = (Double) expected;
111             Double act = (Double) actual;
112             if (isNumber(exp) && isNumber(act) && exp != 0) { // show difference as hex
113                 actL = Double.doubleToLongBits(act);
114                 expL = Double.doubleToLongBits(exp);
115                 if (Math.abs(actL-expL)==1) {
116                     // Not 100% sure off-by-one errors are allowed everywhere, so only allow for these methods
117                     if (methodName.equals("toRadians") || methodName.equals("atan2")) {
118                         return;
119                     }
120                 }
121                 format = "%016x";
122             }
123         } else if (expected instanceof Float ){
124             Float exp = (Float) expected;
125             Float act = (Float) actual;
126             if (isNumber(exp) && isNumber(act) && exp != 0) { // show difference as hex
127                 actL = Float.floatToIntBits(act);
128                 expL = Float.floatToIntBits(exp);
129                 format = "%08x";
130             }
131         }
132         StringBuilder sb = new StringBuilder();
133         sb.append(mathMethod.getReturnType().getSimpleName());
134         sb.append(" ");
135         sb.append(methodName);
136         sb.append("(");
137         String sep = "";
138         for(Object o : params){
139             sb.append(sep);
140             sb.append(o);
141             sep=", ";
142         }
143         sb.append(") expected ");
144         if (format != null){
145             sb.append(String.format(format, expL));
146         } else {
147             sb.append(expected);
148         }
149         sb.append(" actual ");
150         if (format != null){
151             sb.append(String.format(format, actL));
152         } else {
153             sb.append(actual);
154         }
155         sb.append(" entries ");
156         sb.append(Arrays.toString(entries));
157         String message = sb.toString();
158         final boolean fatal = true;
159         if (fatal) {
160             Assert.fail(message);
161         } else {
162             System.out.println(message);
163         }
164     }
165 
166     private static void callMethods(Method mathMethod, Method fastMethod,
167             Object[] params, int[] entries) throws IllegalAccessException {
168         try {
169             Object expected;
170             try {
171                 expected = mathMethod.invoke(mathMethod, params);
172             } catch (InvocationTargetException ite) {
173                 expected = ite.getCause();
174             }
175             Object actual;
176             try {
177                 actual = fastMethod.invoke(mathMethod, params);
178             } catch (InvocationTargetException ite) {
179                 actual = ite.getCause();
180             }
181             if (expected instanceof ArithmeticException) {
182                 Assert.assertEquals(MathRuntimeException.class, actual.getClass());
183             } else  if (!expected.equals(actual)) {
184                 reportFailedResults(mathMethod, params, expected, actual, entries);
185             }
186         } catch (IllegalArgumentException e) {
187             System.out.println(mathMethod);
188             System.out.println(fastMethod);
189             System.out.print("params = ");
190             for (Object o : params) {
191                 System.out.print(" " + o);
192             }
193             System.out.println();
194             System.out.print("entries = ");
195             for (int i : entries) {
196                 System.out.print(" " + i);
197             }
198             System.out.println();
199             e.printStackTrace();
200             Assert.fail(mathMethod+" "+e);
201         }
202     }
203 
204     private static void setupMethodCall(Method mathMethod, Method fastMethod,
205                                         Type[] types, Object[][] valueArrays) throws Exception {
206         Object[] params = new Object[types.length];
207         int[] entries = new int[types.length];
208         for (int i = 0; i < params.length; ++i) {
209             for (int j = 0; j < valueArrays[i].length; ++j) {
210                 Object d = valueArrays[i][j];
211                 params[i] = d;
212                 entries[i] = j;
213             }
214         }
215         callMethods(mathMethod, fastMethod, params, entries);
216     }
217 
218     @Parameters
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 }