| /* |
| * 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.math3.util; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.lang.reflect.Type; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| import org.apache.commons.math3.exception.MathArithmeticException; |
| import org.junit.Assert; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameters; |
| |
| /** |
| * Test to compare FastMath results against StrictMath results for boundary values. |
| * <p> |
| * Running all tests independently: <br/> |
| * {@code mvn test -Dtest=FastMathStrictComparisonTest}<br/> |
| * or just run tests against a single method (e.g. scalb):<br/> |
| * {@code mvn test -Dtest=FastMathStrictComparisonTest -DargLine="-DtestMethod=scalb"} |
| */ |
| @SuppressWarnings("boxing") |
| @RunWith(Parameterized.class) |
| public class FastMathStrictComparisonTest { |
| |
| // Values which often need special handling |
| private static final Double[] DOUBLE_SPECIAL_VALUES = { |
| -0.0, +0.0, // 1,2 |
| Double.NaN, // 3 |
| Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, // 4,5 |
| -Double.MAX_VALUE, Double.MAX_VALUE, // 6,7 |
| // decreasing order of absolute value to help catch first failure |
| -Precision.EPSILON, Precision.EPSILON, // 8,9 |
| -Precision.SAFE_MIN, Precision.SAFE_MIN, // 10,11 |
| -Double.MIN_VALUE, Double.MIN_VALUE, // 12,13 |
| }; |
| |
| private static final Float [] FLOAT_SPECIAL_VALUES = { |
| -0.0f, +0.0f, // 1,2 |
| Float.NaN, // 3 |
| Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, // 4,5 |
| Float.MIN_VALUE, Float.MAX_VALUE, // 6,7 |
| -Float.MIN_VALUE, -Float.MAX_VALUE, // 8,9 |
| }; |
| |
| private static final Object [] LONG_SPECIAL_VALUES = { |
| -1,0,1, // 1,2,3 |
| Long.MIN_VALUE, Long.MAX_VALUE, // 4,5 |
| }; |
| |
| private static final Object[] INT_SPECIAL_VALUES = { |
| -1,0,1, // 1,2,3 |
| Integer.MIN_VALUE, Integer.MAX_VALUE, // 4,5 |
| }; |
| |
| private final Method mathMethod; |
| private final Method fastMethod; |
| private final Type[] types; |
| private final Object[][] valueArrays; |
| |
| public FastMathStrictComparisonTest(Method m, Method f, Type[] types, Object[][] data) throws Exception{ |
| this.mathMethod=m; |
| this.fastMethod=f; |
| this.types=types; |
| this.valueArrays=data; |
| } |
| |
| @Test |
| public void test1() throws Exception{ |
| setupMethodCall(mathMethod, fastMethod, types, valueArrays); |
| } |
| private static boolean isNumber(Double d) { |
| return !(d.isInfinite() || d.isNaN()); |
| } |
| |
| private static boolean isNumber(Float f) { |
| return !(f.isInfinite() || f.isNaN()); |
| } |
| |
| private static void reportFailedResults(Method mathMethod, Object[] params, Object expected, Object actual, int[] entries){ |
| final String methodName = mathMethod.getName(); |
| String format = null; |
| long actL=0; |
| long expL=0; |
| if (expected instanceof Double) { |
| Double exp = (Double) expected; |
| Double act = (Double) actual; |
| if (isNumber(exp) && isNumber(act) && exp != 0) { // show difference as hex |
| actL = Double.doubleToLongBits(act); |
| expL = Double.doubleToLongBits(exp); |
| if (Math.abs(actL-expL)==1) { |
| // Not 100% sure off-by-one errors are allowed everywhere, so only allow for these methods |
| if (methodName.equals("toRadians") || methodName.equals("atan2")) { |
| return; |
| } |
| } |
| format = "%016x"; |
| } |
| } else if (expected instanceof Float ){ |
| Float exp = (Float) expected; |
| Float act = (Float) actual; |
| if (isNumber(exp) && isNumber(act) && exp != 0) { // show difference as hex |
| actL = Float.floatToIntBits(act); |
| expL = Float.floatToIntBits(exp); |
| format = "%08x"; |
| } |
| } |
| StringBuilder sb = new StringBuilder(); |
| sb.append(mathMethod.getReturnType().getSimpleName()); |
| sb.append(" "); |
| sb.append(methodName); |
| sb.append("("); |
| String sep = ""; |
| for(Object o : params){ |
| sb.append(sep); |
| sb.append(o); |
| sep=", "; |
| } |
| sb.append(") expected "); |
| if (format != null){ |
| sb.append(String.format(format, expL)); |
| } else { |
| sb.append(expected); |
| } |
| sb.append(" actual "); |
| if (format != null){ |
| sb.append(String.format(format, actL)); |
| } else { |
| sb.append(actual); |
| } |
| sb.append(" entries "); |
| sb.append(Arrays.toString(entries)); |
| String message = sb.toString(); |
| final boolean fatal = true; |
| if (fatal) { |
| Assert.fail(message); |
| } else { |
| System.out.println(message); |
| } |
| } |
| |
| private static void callMethods(Method mathMethod, Method fastMethod, |
| Object[] params, int[] entries) throws IllegalAccessException { |
| try { |
| Object expected; |
| try { |
| expected = mathMethod.invoke(mathMethod, params); |
| } catch (InvocationTargetException ite) { |
| expected = ite.getCause(); |
| } |
| Object actual; |
| try { |
| actual = fastMethod.invoke(mathMethod, params); |
| } catch (InvocationTargetException ite) { |
| actual = ite.getCause(); |
| } |
| if (expected instanceof ArithmeticException) { |
| Assert.assertEquals(MathArithmeticException.class, actual.getClass()); |
| } else if (!expected.equals(actual)) { |
| reportFailedResults(mathMethod, params, expected, actual, entries); |
| } |
| } catch (IllegalArgumentException e) { |
| Assert.fail(mathMethod+" "+e); |
| } |
| } |
| |
| private static void setupMethodCall(Method mathMethod, Method fastMethod, |
| Type[] types, Object[][] valueArrays) throws Exception { |
| Object[] params = new Object[types.length]; |
| int entry1 = 0; |
| int[] entries = new int[types.length]; |
| for(Object d : valueArrays[0]) { |
| entry1++; |
| params[0] = d; |
| entries[0] = entry1; |
| if (params.length > 1){ |
| int entry2 = 0; |
| for(Object d1 : valueArrays[1]) { |
| entry2++; |
| params[1] = d1; |
| entries[1] = entry2; |
| callMethods(mathMethod, fastMethod, params, entries); |
| } |
| } else { |
| callMethods(mathMethod, fastMethod, params, entries); |
| } |
| } |
| } |
| |
| @Parameters |
| public static List<Object[]> data() throws Exception { |
| String singleMethod = System.getProperty("testMethod"); |
| List<Object[]> list = new ArrayList<Object[]>(); |
| for(Method mathMethod : StrictMath.class.getDeclaredMethods()) { |
| method: |
| if (Modifier.isPublic(mathMethod.getModifiers())){// Only test public methods |
| Type []types = mathMethod.getGenericParameterTypes(); |
| if (types.length >=1) { // Only check methods with at least one parameter |
| try { |
| // Get the corresponding FastMath method |
| Method fastMethod = FastMath.class.getDeclaredMethod(mathMethod.getName(), (Class[]) types); |
| if (Modifier.isPublic(fastMethod.getModifiers())) { // It must be public too |
| if (singleMethod != null && !fastMethod.getName().equals(singleMethod)) { |
| break method; |
| } |
| Object [][] values = new Object[types.length][]; |
| int index = 0; |
| for(Type t : types) { |
| if (t.equals(double.class)){ |
| values[index]=DOUBLE_SPECIAL_VALUES; |
| } else if (t.equals(float.class)) { |
| values[index]=FLOAT_SPECIAL_VALUES; |
| } else if (t.equals(long.class)) { |
| values[index]=LONG_SPECIAL_VALUES; |
| } else if (t.equals(int.class)) { |
| values[index]=INT_SPECIAL_VALUES; |
| } else { |
| System.out.println("Cannot handle class "+t+" for "+mathMethod); |
| break method; |
| } |
| index++; |
| } |
| // System.out.println(fastMethod); |
| /* |
| * The current implementation runs each method as a separate test. |
| * Could be amended to run each value as a separate test |
| */ |
| list.add(new Object[]{mathMethod, fastMethod, types, values}); |
| // setupMethodCall(mathMethod, fastMethod, params, data); |
| } else { |
| System.out.println("Cannot find public FastMath method corresponding to: "+mathMethod); |
| } |
| } catch (NoSuchMethodException e) { |
| System.out.println("Cannot find FastMath method corresponding to: "+mathMethod); |
| } |
| } |
| } |
| } |
| return list; |
| } |
| } |