All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.plumelib.util.FuzzyFloat Maven / Gradle / Ivy

There is a newer version: 1.10.0
Show newest version
package org.plumelib.util;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Comparator;
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
import org.checkerframework.dataflow.qual.Pure;

/**
 * Routines for doing approximate ('fuzzy') floating-point comparisons. Those are comparisons that
 * only require the floating-point numbers to be relatively close to one another to be equal, rather
 * than exactly equal.
 *
 * 

Floating-point numbers are compared for equality by dividing them by one another and comparing * the ratio. By default, they must be within 0.0001 (0.01%) to be considered equal. * *

Zero is never considered equal to a non-zero number, no matter how small its value. * *

Two NaN floats are not considered equal (consistent with the == operator). */ public class FuzzyFloat { /** Default relative difference between two values such that this class considers them equal. */ static final double DEFAULT_RELATIVE_RATIO = .0001; /** Minimum ratio between two floats, such that this class considers them equal. */ double minRatio; /** Maximum ratio between two floats, such that this class considers them equal. */ double maxRatio; /** * True if this class does approximate (fuzzy) arithmetic comparisons. If false, this class does * exact matching * *

ratio test turned off. This occurs exactly if the class is instantiated with the relative * difference 0. */ boolean exactComparisons = false; /** Creates a FuzzyFloat with the default relativeRatio value of .0001. */ public FuzzyFloat() { setRelativeRatio(DEFAULT_RELATIVE_RATIO); } /** * Creates a FuzzyFloat. Specify the specific relative difference allowed between two floats in * order for them to be equal. The default is 0.0001. A relative diff of zero, disables it (i.e., * this class's methods work just like regular Java arithmetic comparisons). * * @param relativeRatio the relative diff to use; see {@link #setRelativeRatio} */ public FuzzyFloat(double relativeRatio) { setRelativeRatio(relativeRatio); } /** * Set all the fields of this class. * * @param relativeRatio the new relative diff to use; a number near zero (zero is also permitted, * which requires exact matching rather than permitting fuzzy matching) * @see #FuzzyFloat */ public void setRelativeRatio(@UnknownInitialization FuzzyFloat this, double relativeRatio) { minRatio = 1 - relativeRatio; maxRatio = 1 + relativeRatio; exactComparisons = (relativeRatio == 0.0); // System.out.println ("minRatio = " + minRatio + ", maxRatio = " // + maxRatio); } /** * Test d1 and d2 for equality using the current ratio. Two NaN floats are not considered equal * (consistent with the == operator). * *

Note that if one of the numbers if 0.0, then the other number must be less than the square * of the fuzzy ratio. This policy accommodates roundoff errors in floating-point values. * * @param d1 the first value to compare * @param d2 the second value to compare * @return true if d1 and d2 are considered equal, false otherwise */ @Pure public boolean eq(double d1, double d2) { // NaNs are not considered equal. if (Double.isNaN(d1) && Double.isNaN(d2)) { return false; } // if zero was specified for a ratio, don't do the divide. You might // get slightly different answers. And this should be faster. if (exactComparisons) { return d1 == d2; } // slightly more efficient for matches and catches positive and negative // infinity (which match in this test, but not below) if (d1 == d2) { return true; } // When one number is 0, require that the other is less than the square of the fuzzy ratio. // This heuristic accommodates roundoff errors in floating-point values. if (d1 == 0.0 || d2 == 0.0) { double zeroTolerance = Math.pow((maxRatio - 1), 2); if (d1 == 0.0) { return Math.abs(d2) < zeroTolerance; } else { return Math.abs(d1) < zeroTolerance; } } double ratio = d1 / d2; return (ratio >= minRatio) && (ratio <= maxRatio); } /** * Test d1 and d2 for non-equality using the current ratio. * * @param d1 the first value to compare * @param d2 the second value to compare * @return whether d1 and d2 are non-equal * @see #eq */ @Pure public boolean ne(double d1, double d2) { return !eq(d1, d2); } /** * Test d1 and d2 for {@code d1 < d2}. If d1 is equal to d2 using the current ratio this returns * false. * * @param d1 the first value to compare * @param d2 the second value to compare * @return whether d1 < d2 * @see #eq */ @Pure public boolean lt(double d1, double d2) { return (d1 < d2) && ne(d1, d2); } /** * Test d1 and d2 for {@code d1 <= d2}. If d1 is equal to d2 using the current ratio, this returns * true. * * @param d1 the first value to compare * @param d2 the second value to compare * @return whether d1 ≤ d2 * @see #eq */ @Pure public boolean lte(double d1, double d2) { return (d1 <= d2) || eq(d1, d2); } /** * test d1 and d2 for {@code d1 > d2}. IF d1 is equal to d2 using the current ratio, this returns * false. * * @param d1 the first value to compare * @param d2 the second value to compare * @return whether d1 > d2 * @see #eq */ @Pure public boolean gt(double d1, double d2) { return (d1 > d2) && ne(d1, d2); } /** * test d1 and d2 for {@code d1 >= d2}. If d1 is equal to d2 using the current ratio, this returns * true. * * @param d1 the first value to compare * @param d2 the second value to compare * @return whether d1 ≥ d2 * @see #eq */ @Pure public boolean gte(double d1, double d2) { return (d1 >= d2) || eq(d1, d2); } /** * Searches for the first occurrence of elt in a. elt is considered equal to a[i] if it passes the * {@link #eq} test. * * @param a the array to search * @param elt the element to search for * @return the first index containing the specified element, or -1 if the element is not found in * the array * @see java.util.ArrayList#indexOf(java.lang.Object) */ @Pure public int indexOf(double[] a, double elt) { for (int i = 0; i < a.length; i++) { if (eq(elt, a[i])) { return i; } } return -1; } /** * Searches for the first subsequence of {@code a} that matches {@code sub} elementwise. Elements * of {@code sub} are considered to match elements of {@code a} if they pass the {@link #eq} test. * * @param a the sequence to search in * @param sub the sequence to search for * @return the first index whose subarray is equal to the specified array or -1 if no such * subarray is found in the array * @see java.util.ArrayList#indexOf(java.lang.Object) * @see java.lang.String#indexOf(java.lang.String) */ @Pure public int indexOf(double[] a, double[] sub) { int aIndexMax = a.length - sub.length; outer: for (int i = 0; i <= aIndexMax; i++) { for (int j = 0; j < sub.length; j++) { if (ne(a[i + j], sub[j])) { continue outer; } } return i; } return -1; } /** * Determines whether or not a1 and a2 are set equivalent (contain only the same elements). * Element comparison uses {@link #eq}. * *

Note that this implementation is optimized for cases where the elements are actually the * same, since it does a sort of both arrays before starting the comparisons. * * @param a1 the first set to compare * @param a2 the second set to compare * @return true if a1 and a2 are set equivalent, false otherwise */ @SuppressWarnings({"allcheckers:purity", "lock"}) // side effect to local state (arrays) @Pure public boolean isElemMatch(double[] a1, double[] a2) { // don't change our parameters a1 = a1.clone(); a2 = a2.clone(); Arrays.sort(a1); Arrays.sort(a2); // look for elements of a2 in a1 int start = 0; outer1: for (int i = 0; i < a2.length; i++) { double val = a2[i]; for (int j = start; j < a1.length; j++) { if (eq(val, a1[j])) { start = j; continue outer1; } if (val < a1[j]) { // System.out.println ("isElemMatch: " + val + " " + a1[j]); return false; } } // System.out.println ("isElemMatch: " + i); return false; } // look for elements of a1 in a2 start = 0; outer2: for (int i = 0; i < a1.length; i++) { double val = a1[i]; for (int j = start; j < a2.length; j++) { if (eq(val, a2[j])) { start = j; continue outer2; } if (val < a2[j]) { // System.out.println ("isElemMatch: " + val + " " + a2[j]); return false; } } // System.out.println ("isElemMatch: " + i); return false; } return true; } // Slightly more efficient method that will miss some matches // int i = 0; // int j = 0; // while (i < a1.length && j < a2.length) { // if (ne (a1[i], a2[j])) { // System.out.println ("isElemMatch: " + a1[i] + " " + a2[j]); // return false; // } // double val = a1[i]; // i++; // while ((i < a1.length) && (eq (a1[i], val))) { // i++; // } // j++; // while ((j < a2.length) && (eq (a2[j], val))) { // j++; // } // } // // if there are any elements left, then they don't match. // if ((i != a1.length) || (j != a2.length)) { // System.out.println ("isElemMatch: " + i + " " + j); // return false; // } // return true; // } /** Lexically compares two double arrays. */ public class DoubleArrayComparatorLexical implements Comparator, Serializable { /** Unique identifier for serialization. If you add or remove fields, change this number. */ static final long serialVersionUID = 20150812L; /** Create a new DoubleArrayComparatorLexical. */ public DoubleArrayComparatorLexical() {} /** * Lexically compares o1 and o2 as double arrays. * * @param a1 the first array to compare * @param a2 the second array to compare * @return positive if o1 > 02, 0 if o1 == o2, negative if o1 < o2 */ @Pure @Override public int compare(double[] a1, double[] a2) { if (a1 == a2) { return 0; } int len = Math.min(a1.length, a2.length); for (int i = 0; i < len; i++) { if (ne(a1[i], a2[i])) { return (a1[i] > a2[i]) ? 1 : -1; } } return a1.length - a2.length; } } /** * Determines whether smaller is a subset of bigger. Element comparison uses {@link #eq}. * *

Note that this implementation is optimized for cases where the elements are actually the * same, since it does a sort of both arrays before starting the comparisons. * * @param smaller the possibly-smaller subset * @param bigger the possibly-larger set * @return true if smaller is a subset (each element of smaller is also a element of bigger) of * bigger, false otherwise */ @SuppressWarnings({"allcheckers:purity", "lock"}) // side effect to local state (arrays) @Pure public boolean isSubset(double[] smaller, double[] bigger) { // don't change our parameters smaller = smaller.clone(); bigger = bigger.clone(); Arrays.sort(smaller); Arrays.sort(bigger); // look for elements of smaller in bigger int start = 0; outer1: for (int i = 0; i < smaller.length; i++) { double val = smaller[i]; for (int j = start; j < bigger.length; j++) { if (eq(val, bigger[j])) { start = j; continue outer1; } if (val < bigger[j]) { return false; } } return false; } return true; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy