/** * Copyright (C) 2019, 2021, 2024, 2025 Adrien Hopkins * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package sevenUnits.utils; import java.math.BigDecimal; /** * A class that contains methods to compare float and double values. * * @author Adrien Hopkins * @since 2019-03-18 * @since v0.2.0 */ public final class DecimalComparison { /** * The value used for double comparison. If two double values are within this * value multiplied by the larger value, they are considered equal. * * @since 2019-03-18 * @since v0.2.0 */ public static final double DOUBLE_EPSILON = 1.0e-15; /** * The value used for float comparison. If two float values are within this * value multiplied by the larger value, they are considered equal. * * @since 2019-03-18 * @since v0.2.0 */ public static final float FLOAT_EPSILON = 1.0e-6f; /** * Tests for equality of double values using {@link #DOUBLE_EPSILON}. *

* WARNING: this method is not technically transitive. If a * and b are off by slightly less than {@code epsilon * max(abs(a), abs(b))}, * and b and c are off by slightly less than * {@code epsilon * max(abs(b), abs(c))}, then equals(a, b) and equals(b, c) * will both return true, but equals(a, c) will return false. However, this * situation is very unlikely to ever happen in a real programming situation. *

* If this does become a concern, some ways to solve this problem: *

    *
  1. Raise the value of epsilon using * {@link #equals(double, double, double)} (this does not make a violation of * transitivity impossible, it just significantly reduces the chances of it * happening) *
  2. Use {@link BigDecimal} instead of {@code double} (this will make a * violation of transitivity 100% impossible) *
* * @param a first value to test * @param b second value to test * @return whether they are equal * @since 2019-03-18 * @since v0.2.0 */ public static boolean equals(final double a, final double b) { return DecimalComparison.equals(a, b, DOUBLE_EPSILON); } /** * Tests for double equality using a custom epsilon value. * *

* WARNING: this method is not technically transitive. If a * and b are off by slightly less than {@code epsilon * max(abs(a), abs(b))}, * and b and c are off by slightly less than * {@code epsilon * max(abs(b), abs(c))}, then equals(a, b) and equals(b, c) * will both return true, but equals(a, c) will return false. However, this * situation is very unlikely to ever happen in a real programming situation. *

* If this does become a concern, some ways to solve this problem: *

    *
  1. Raise the value of epsilon (this does not make a violation of * transitivity impossible, it just significantly reduces the chances of it * happening) *
  2. Use {@link BigDecimal} instead of {@code double} (this will make a * violation of transitivity 100% impossible) *
* * @param a first value to test * @param b second value to test * @param epsilon allowed difference * @return whether they are equal * @since 2019-03-18 * @since v0.2.0 */ public static boolean equals(final double a, final double b, final double epsilon) { return Math.abs(a - b) <= epsilon * Math.max(Math.abs(a), Math.abs(b)); } /** * Tests for equality of float values using {@link #FLOAT_EPSILON}. * *

* WARNING: this method is not technically transitive. If a * and b are off by slightly less than {@code epsilon * max(abs(a), abs(b))}, * and b and c are off by slightly less than * {@code epsilon * max(abs(b), abs(c))}, then equals(a, b) and equals(b, c) * will both return true, but equals(a, c) will return false. However, this * situation is very unlikely to ever happen in a real programming situation. *

* If this does become a concern, some ways to solve this problem: *

    *
  1. Raise the value of epsilon using {@link #equals(float, float, float)} * (this does not make a violation of transitivity impossible, it just * significantly reduces the chances of it happening) *
  2. Use {@link BigDecimal} instead of {@code float} (this will make a * violation of transitivity 100% impossible) *
* * @param a first value to test * @param b second value to test * @return whether they are equal * @since 2019-03-18 * @since v0.2.0 */ public static boolean equals(final float a, final float b) { return DecimalComparison.equals(a, b, FLOAT_EPSILON); } /** * Tests for float equality using a custom epsilon value. * *

* WARNING: this method is not technically transitive. If a * and b are off by slightly less than {@code epsilon * max(abs(a), abs(b))}, * and b and c are off by slightly less than * {@code epsilon * max(abs(b), abs(c))}, then equals(a, b) and equals(b, c) * will both return true, but equals(a, c) will return false. However, this * situation is very unlikely to ever happen in a real programming situation. *

* If this does become a concern, some ways to solve this problem: *

    *
  1. Raise the value of epsilon (this does not make a violation of * transitivity impossible, it just significantly reduces the chances of it * happening) *
  2. Use {@link BigDecimal} instead of {@code float} (this will make a * violation of transitivity 100% impossible) *
* * @param a first value to test * @param b second value to test * @param epsilon allowed difference * @return whether they are equal * @since 2019-03-18 * @since v0.2.0 */ public static boolean equals(final float a, final float b, final float epsilon) { return Math.abs(a - b) <= epsilon * Math.max(Math.abs(a), Math.abs(b)); } /** * Tests for equality of {@code UncertainDouble} values using * {@link #DOUBLE_EPSILON}. *

* WARNING: this method is not technically transitive. If a * and b are off by slightly less than {@code epsilon * max(abs(a), abs(b))}, * and b and c are off by slightly less than * {@code epsilon * max(abs(b), abs(c))}, then equals(a, b) and equals(b, c) * will both return true, but equals(a, c) will return false. However, this * situation is very unlikely to ever happen in a real programming situation. *

* If this does become a concern, some ways to solve this problem: *

    *
  1. Raise the value of epsilon using * {@link #equals(UncertainDouble, UncertainDouble, double)} (this does not * make a violation of transitivity impossible, it just significantly reduces * the chances of it happening) *
  2. Use {@link BigDecimal} instead of {@code double} (this will make a * violation of transitivity 100% impossible) *
* * @param a first value to test * @param b second value to test * @return whether they are equal * @since 2020-09-07 * @since v0.3.0 */ public static boolean equals(final UncertainDouble a, final UncertainDouble b) { return DecimalComparison.equals(a.value(), b.value()) && DecimalComparison.equals(a.uncertainty(), b.uncertainty()); } /** * Tests for {@code UncertainDouble} equality using a custom epsilon value. * *

* WARNING: this method is not technically transitive. If a * and b are off by slightly less than {@code epsilon * max(abs(a), abs(b))}, * and b and c are off by slightly less than * {@code epsilon * max(abs(b), abs(c))}, then equals(a, b) and equals(b, c) * will both return true, but equals(a, c) will return false. However, this * situation is very unlikely to ever happen in a real programming situation. *

* If this does become a concern, some ways to solve this problem: *

    *
  1. Raise the value of epsilon (this does not make a violation of * transitivity impossible, it just significantly reduces the chances of it * happening) *
  2. Use {@link BigDecimal} instead of {@code double} (this will make a * violation of transitivity 100% impossible) *
* * @param a first value to test * @param b second value to test * @param epsilon allowed difference * @return whether they are equal * @since 2019-03-18 * @since v0.2.0 */ public static boolean equals(final UncertainDouble a, final UncertainDouble b, final double epsilon) { return DecimalComparison.equals(a.value(), b.value(), epsilon) && DecimalComparison.equals(a.uncertainty(), b.uncertainty(), epsilon); } // You may NOT get any DecimalComparison instances private DecimalComparison() { throw new AssertionError(); } }