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 }