/** * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.math.impl; import java.util.Locale; import org.slf4j.Logger; import org.slf4j.LoggerFactory; // NOTE: This is from OG-Maths /** * Tests for values being equal allowing for a level of floating point fuzz * Based on the OG-Maths C++ fuzzy equals code . */ public class FuzzyEquals { private static boolean __LOCALDEBUG = false; private static boolean DEBUG = false; private static double float64_eps; private static double default_tolerance; static { float64_eps = float64_t_machineEpsilon(); default_tolerance = 10 * float64_eps; } /** * The logger instance */ private static Logger s_log = LoggerFactory.getLogger(FuzzyEquals.class); /** * Gets machine precision for double precision floating point numbers on this machine. * @return machine precision for double precision floating point numbers on this machine. */ public static double getEps() { return float64_eps; } /** * Get the default tolerance used in this class. * @return the default tolerance. */ public static double getDefaultTolerance() { return default_tolerance; } /** * Checks if two double precision floating point numbers are approximately "equal" * @param val1 the first value * @param val2 the second value * @param maxabserror determines the minimum threshold for "equal" in terms of the two numbers being very small in magnitude. * @param maxrelerror determines the minimum threshold for "equal" in terms of the relative magnitude of the numbers. * i.e. invariant of the magnitude of the numbers what is the maximum level of magnitude difference acceptable. * @return true if they are considered equal, else false */ public static boolean SingleValueFuzzyEquals(double val1, double val2, double maxabserror, double maxrelerror) { if (__LOCALDEBUG) { DEBUG_PRINT("FuzzyEquals: Comparing %24.16f and %24.16f\n", val1, val2); } if (Double.isNaN(val1)) { if (__LOCALDEBUG) { DEBUG_PRINT("FuzzyEquals: Failed as value 1 is NaN\n"); } return false; } if (Double.isNaN(val2)) { if (__LOCALDEBUG) { DEBUG_PRINT("FuzzyEquals: Failed as value 2 is NaN\n"); } return false; } // deal with infs in debug mode if (__LOCALDEBUG) { if (DEBUG) { boolean val1isinf = Double.isInfinite(val1); boolean val2isinf = Double.isInfinite(val2); if (val1isinf || val2isinf) { if (val1isinf && val2isinf) { if (Math.signum(val2) == Math.signum(val1)) { DEBUG_PRINT("FuzzyEquals: Inf Branch. Success as both inf of same sign\n"); return true; } } DEBUG_PRINT("FuzzyEquals: Inf Branch. Fail, non matching infs\n"); return false; } } } if (val1 == val2) { return true; // (+/-)inf compares == as does (+/-)0.e0 } // check if they are below max absolute error bounds (i.e. small in the first place) double diff = (val1 - val2); if (maxabserror > Math.abs(diff)) { if (__LOCALDEBUG) { DEBUG_PRINT("FuzzyEquals: Match as below diff bounds. maxabserror > diff. (%24.16f >%24.16f)\n", maxabserror, Math.abs(diff)); } return true; } if (__LOCALDEBUG) { DEBUG_PRINT("FuzzyEquals: Failed as diff > maxabserror. (%24.16f > %24.16f)\n", Math.abs(diff), maxabserror); } // check if they are within a relative error bound, div difference by largest of the 2 double divisor = Math.abs(val1) > Math.abs(val2) ? val1 : val2; double relerror = Math.abs(diff / divisor); if (maxrelerror > relerror) { if (__LOCALDEBUG) { DEBUG_PRINT("FuzzyEquals: Match as maxrelerror > relerror. (%24.16f > %24.16f)\n", maxrelerror, relerror); } return true; } ; if (__LOCALDEBUG) { DEBUG_PRINT("FuzzyEquals: Fail as relerror > maxrelerror. (%24.16f > %24.16f)\n", relerror, maxrelerror); } return false; } /** * Checks if two double precision floating point numbers are approximately "equal" * Default values are used for tolerances * @param val1 the first value * @param val2 the second value * @return true if they are considered equal, else false */ public static boolean SingleValueFuzzyEquals(double val1, double val2) { return SingleValueFuzzyEquals(val1, val2, default_tolerance, default_tolerance); } /** * Checks if two double precision floating point arrays are approximately "equal" * Equal means the arrays have values the are considered fuzzy equals appearing in the same order * and the arrays the same length. * * @param arr1 the first value * @param arr2 the second value * @param maxabserror determines the minimum threshold for "equal" in terms of the two numbers being very small in magnitude. * @param maxrelerror determines the minimum threshold for "equal" in terms of the relative magnitude of the numbers. * i.e. invariant of the magnitude of the numbers what is the maximum level of magnitude difference acceptable. * @return true if they are considered equal, else false */ public static boolean ArrayFuzzyEquals(double[] arr1, double[] arr2, double maxabserror, double maxrelerror) { if (arr1.length != arr2.length) { return false; } for (int i = 0; i < arr1.length; i++) { if (!SingleValueFuzzyEquals(arr1[i], arr2[i], maxabserror, maxrelerror)) return false; } return true; } /** * Checks if two double precision floating point arrays are approximately "equal" * Equal means the arrays have values the are considered fuzzy equals appearing in the same order * and the arrays the same length. * Default values are used for tolerances. * * @param arr1 the first value * @param arr2 the second value * @return true if they are considered equal, else false */ public static boolean ArrayFuzzyEquals(double[] arr1, double[] arr2) { return ArrayFuzzyEquals(arr1, arr2, default_tolerance, default_tolerance); } /** * Checks if two double precision floating point array of arrays are approximately "equal" * Equal means the arrays have values the are considered fuzzy equals appearing in the same order and the arrays the same dimension. * Default values are used for tolerances. * @param arr1 the first value * @param arr2 the second value * @return true if they are considered equal, else false */ public static boolean ArrayFuzzyEquals(double[][] arr1, double[][] arr2) { return ArrayFuzzyEquals(arr1, arr2, default_tolerance, default_tolerance); } /** * Checks if two double precision floating point array of arrays are approximately "equal" * Equal means the arrays have values the are considered fuzzy equals appearing in the same order * and the arrays the same dimension. * * @param arr1 the first value * @param arr2 the second value * @param maxabserror determines the minimum threshold for "equal" in terms of the two numbers being very small in magnitude. * @param maxrelerror determines the minimum threshold for "equal" in terms of the relative magnitude of the numbers. * i.e. invariant of the magnitude of the numbers what is the maximum level of magnitude difference acceptable. * @return true if they are considered equal, else false */ public static boolean ArrayFuzzyEquals(double[][] arr1, double[][] arr2, double maxabserror, double maxrelerror) { if (arr1.length != arr2.length) { return false; } int rows = arr1.length; for (int k = 0; k < rows; k++) { if (arr1[k].length != arr2[k].length) { return false; } if (ArrayFuzzyEquals(arr1[k], arr2[k], maxabserror, maxrelerror) == false) { return false; } } return true; } /** * Debug helpers * @param str */ private static void DEBUG_PRINT(String str) { s_log.debug(str); } private static void DEBUG_PRINT(String str, double a, double b) { s_log.debug(String.format(Locale.ENGLISH, str, a, b)); } private static double float64_t_machineEpsilon() { double eps = 1.e0; while ((1.e0 + (eps / 2.e0)) != 1.e0) { eps /= 2.e0; } return eps; } }