View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.statistics.distribution;
19  
20  import java.util.Objects;
21  import java.util.function.Supplier;
22  import org.apache.commons.numbers.core.Precision;
23  
24  /**
25   * Creates instances of {@link DoubleTolerance}.
26   *
27   * <p>Floating-point comparisons are based on
28   * {@link Precision org.apache.commons.numbers.core.Precision}.
29   *
30   * <p>This class is public and has public methods to allow testing within the other modules.
31   */
32  public final class DoubleTolerances {
33      /** A tolerance for numerical equality. This is immutable. */
34      private static final DoubleTolerance EQUALS = new AbstractDoubleTolerance() {
35          @Override
36          public boolean test(double a, double b) {
37              return Double.doubleToLongBits(a) == Double.doubleToLongBits(b);
38          }
39  
40          @Override
41          public String get() {
42              return "exact";
43          }
44      };
45  
46      /** No instances. */
47      private DoubleTolerances() {}
48  
49      /**
50       * Creates a {@link DoubleTolerance} instance that uses exact binary equality.
51       *
52       * <p>Notes:
53       * <ul>
54       * <li>{@code 0.0} and {@code -0.0} are not considered equal.
55       * <li>NaNs are considered equal.
56       * </ul>
57       *
58       * @return an instance.
59       * @see Double#equals(Object)
60       * @see Double#doubleToLongBits(double)
61       */
62      public static DoubleTolerance equals() {
63          return EQUALS;
64      }
65  
66      /**
67       * Creates a {@link DoubleTolerance} instance that uses the given range of allowed
68       * error (inclusive) for determining equality.
69       *
70       * <p>Notes:
71       * <ul>
72       * <li>Two float numbers are considered equal if there are {@code (maxUlps - 1)}
73       * (or fewer) floating point numbers between them. Use {@code maxUlps = 0} for
74       * numerical equality.
75       * <li>NaNs are considered equal.
76       * <li>{@code 0.0} and {@code -0.0} are considered equal when {@code maxUlps = 0}.
77       * </ul>
78       *
79       * <p>Use of this method with {@code maxUlps = 0} is slower than using {@link #equals()}
80       * for binary equality. The results are identical with the exception that {@code 0.0} and
81       * {@code -0.0} are considered equal.
82       *
83       * @param maxUlps {@code (maxUlps - 1)} is the number of floating point
84       * values between {@code x} and {@code y}.
85       * @return a new instance.
86       * @throws IllegalArgumentException if {@code eps < 0} or is not finite
87       * @see Precision#equalsIncludingNaN(double, double, int)
88       */
89      public static DoubleTolerance ulps(final int maxUlps) {
90          return new AbstractDoubleTolerance() {
91              @Override
92              public boolean test(double a, double b) {
93                  return Precision.equalsIncludingNaN(a, b, maxUlps);
94              }
95  
96              @Override
97              public String get() {
98                  return "ulp=" + maxUlps;
99              }
100         };
101     }
102 
103     /**
104      * Creates a {@link DoubleTolerance} instance that uses the given absolute epsilon
105      * value for determining equality.
106      *
107      * <p>Notes:
108      * <ul>
109      * <li>Two numbers are considered equal if there are 0
110      * floating point numbers between them, i.e. two adjacent
111      * floating point numbers are considered equal.
112      * <li>NaNs are considered equal.
113      * </ul>
114      *
115      * <p>Use of this method with {@code eps = 0.0} is not recommended as neighbouring
116      * floating points numbers are considered equal. Consider using {@link #equals()}
117      * for binary equality or {@link #ulps(int)} with {@code ulps=0} for numerical equality.
118      *
119      * @param eps Value to use for determining equality.
120      * @return a new instance.
121      * @throws IllegalArgumentException if {@code eps < 0} or is not finite
122      * @see Precision#equalsIncludingNaN(double, double, double)
123      */
124     public static DoubleTolerance absolute(final double eps) {
125         if (!Double.isFinite(eps) ||
126             eps < 0d) {
127             throw new IllegalArgumentException("Invalid epsilon value: " + eps);
128         }
129 
130         return new AbstractDoubleTolerance() {
131             @Override
132             public boolean test(double a, double b) {
133                 return Precision.equalsIncludingNaN(a, b, eps);
134             }
135 
136             @Override
137             public String get() {
138                 return "abs=" + eps;
139             }
140         };
141     }
142 
143     /**
144      * Creates a {@link DoubleTolerance} instance that uses the given relative epsilon
145      * value for determining equality.
146      *
147      * <p>Notes:
148      * <ul>
149      * <li>Two numbers are considered equal if there are 0
150      * floating point numbers between them, i.e. two adjacent
151      * floating point numbers are considered equal; or
152      * the relative difference between them is less than or equal
153      * to the given tolerance.
154      * <li>NaNs are <strong>not</strong> considered equal.
155      * <li>The relative tolerance instance is symmetric; it evaluates the same for
156      * {@code (a, b)} or {@code (b, a)}.
157      * </ul>
158      *
159      * <p>Use of this method with {@code eps = 0.0} is not recommended as neighbouring
160      * floating points numbers are considered equal. Consider using {@link #equals()}
161      * for binary equality or {@link #ulps(int)} with {@code ulps=0} for numerical equality.
162      *
163      * @param eps Value to use for determining equality.
164      * @return a new instance.
165      * @throws IllegalArgumentException if {@code eps < 0} or is not finite
166      * @see Precision#equalsIncludingNaN(double, double, double)
167      */
168     public static DoubleTolerance relative(final double eps) {
169         if (!Double.isFinite(eps) ||
170             eps < 0d) {
171             throw new IllegalArgumentException("Invalid epsilon value: " + eps);
172         }
173 
174         return new AbstractDoubleTolerance() {
175             @Override
176             public boolean test(double a, double b) {
177                 return Precision.equalsWithRelativeTolerance(a, b, eps);
178             }
179 
180             @Override
181             public String get() {
182                 return "rel=" + eps;
183             }
184         };
185     }
186 
187     /**
188      * Custom implementation of the DoubleTolerance interface to allow message formatting
189      * in assertions using {@link Supplier Supplier<String>}.
190      */
191     abstract static class AbstractDoubleTolerance implements DoubleTolerance, Supplier<String> {
192         @Override
193         public DoubleTolerance and(DoubleTolerance other) {
194             return new DoubleAndTolerance(this, other);
195         }
196 
197         @Override
198         public DoubleTolerance negate() {
199             return new DoubleNegateTolerance(this);
200         }
201 
202         @Override
203         public DoubleTolerance or(DoubleTolerance other) {
204             return new DoubleOrTolerance(this, other);
205         }
206 
207         @Override
208         public String toString() {
209             // This is overridden so that the tolerance is converted into a human readable
210             // string using its description. For example this is used by JUnit to display
211             // a DoubleTolerance argument for a ParameterizedTest.
212             return get();
213         }
214     }
215 
216     /**
217      * Represents a logical {@code And} of two tolerances, each tolerance testing the two
218      * input values.
219      *
220      * <p>Supports a description {@link Supplier} for a {@link String}.
221      */
222     static class DoubleAndTolerance extends AbstractDoubleTolerance {
223         /** The first tolerance. */
224         private final DoubleTolerance tolerance1;
225         /** The second tolerance. */
226         private final DoubleTolerance tolerance2;
227 
228         /**
229          * @param tolerance1 The first tolerance
230          * @param tolerance2 The second tolerance
231          */
232         DoubleAndTolerance(DoubleTolerance tolerance1, DoubleTolerance tolerance2) {
233             this.tolerance1 = Objects.requireNonNull(tolerance1, "Tolerance 1 is null");
234             this.tolerance2 = Objects.requireNonNull(tolerance2, "Tolerance 2 is null");
235         }
236 
237         @Override
238         public boolean test(double value1, double value2) {
239             return tolerance1.test(value1, value2) && tolerance2.test(value1, value2);
240         }
241 
242         @Override
243         public String get() {
244             return StringUtils.andToString(tolerance1, tolerance2);
245         }
246     }
247 
248     /**
249      * Represents a logical {@code Or} of two tolerances, each tolerance testing the two
250      * input values.
251      *
252      * <p>Supports a description {@link Supplier} for a {@link String}.
253      */
254     static class DoubleOrTolerance extends AbstractDoubleTolerance {
255         /** The first tolerance. */
256         private final DoubleTolerance tolerance1;
257         /** The second tolerance. */
258         private final DoubleTolerance tolerance2;
259 
260         /**
261          * @param tolerance1 The first tolerance
262          * @param tolerance2 The second tolerance
263          */
264         DoubleOrTolerance(DoubleTolerance tolerance1, DoubleTolerance tolerance2) {
265             this.tolerance1 = Objects.requireNonNull(tolerance1, "Tolerance 1 is null");
266             this.tolerance2 = Objects.requireNonNull(tolerance2, "Tolerance 2 is null");
267         }
268 
269         @Override
270         public boolean test(double value1, double value2) {
271             return tolerance1.test(value1, value2) || tolerance2.test(value1, value2);
272         }
273 
274         @Override
275         public String get() {
276             return StringUtils.orToString(tolerance1, tolerance2);
277         }
278     }
279 
280     /**
281      * Represents a negation of a tolerance including the string representation using
282      * {@link Supplier} for a {@link String}.
283      */
284     static class DoubleNegateTolerance extends AbstractDoubleTolerance {
285         /** The tolerance. */
286         private final DoubleTolerance tolerance;
287 
288         /**
289          * @param tolerance The tolerance
290          */
291         DoubleNegateTolerance(DoubleTolerance tolerance) {
292             this.tolerance = Objects.requireNonNull(tolerance, "Tolerance is null");
293         }
294 
295         @Override
296         public boolean test(double value1, double value2) {
297             return !tolerance.test(value1, value2);
298         }
299 
300         @Override
301         public DoubleTolerance negate() {
302             // Back to the original
303             return tolerance;
304         }
305 
306         @Override
307         public String get() {
308             return StringUtils.negateToString(tolerance);
309         }
310     }
311 }