| /* |
| * 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.math4.legacy.core.jdkmath; |
| |
| 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.numbers.core.Precision; |
| import org.junit.Assert; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameters; |
| |
| // Unit test should be moved to module "commons-math-core". |
| // [Currently, it can't be because it depends on "legacy" classes.] |
| import org.apache.commons.math4.core.jdkmath.AccurateMath; |
| |
| /** |
| * Test to compare AccurateMath results against StrictMath results for boundary values. |
| * <p> |
| * Running all tests independently: <br> |
| * {@code mvn test -Dtest=AccurateMathStrictComparisonTest}<br> |
| * or just run tests against a single method (e.g. scalb):<br> |
| * {@code mvn test -Dtest=AccurateMathStrictComparisonTest -DargLine="-DtestMethod=scalb"} |
| */ |
| @SuppressWarnings("boxing") |
| @RunWith(Parameterized.class) |
| public class AccurateMathStrictComparisonTest { |
| |
| // 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 AccurateMathStrictComparisonTest(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 { |
| // CHECKSTYLE: stop Regexp |
| System.out.println(message); |
| // CHECKSTYLE: resume Regexp |
| } |
| } |
| |
| 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.assertTrue(actual instanceof ArithmeticException); |
| } 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 { |
| // CHECKSTYLE: stop Regexp |
| String singleMethod = System.getProperty("testMethod"); |
| List<Object[]> list = new ArrayList<>(); |
| 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 AccurateMath method |
| Method fastMethod = AccurateMath.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 AccurateMath method corresponding to: " + mathMethod); |
| } |
| } catch (NoSuchMethodException e) { |
| System.out.println("Cannot find AccurateMath method corresponding to: " + mathMethod); |
| } |
| } |
| } |
| } |
| return list; |
| // CHECKSTYLE: resume Regexp |
| } |
| } |