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  package org.apache.commons.statistics.distribution;
18  
19  import org.apache.commons.rng.UniformRandomProvider;
20  import org.apache.commons.rng.simple.RandomSource;
21  import org.junit.jupiter.api.Assertions;
22  import org.junit.jupiter.api.Test;
23  
24  /**
25   * Test cases for {@link DoubleTolerance} instances create by {@link DoubleTolerances}.
26   */
27  class DoubleTolerancesTest  {
28      @Test
29      void testEquals() {
30          final DoubleTolerance tol = DoubleTolerances.equals();
31          Assertions.assertEquals("exact", StringUtils.toString(tol));
32          // Standard tests
33          Assertions.assertTrue(tol.test(1.5, 1.5));
34          Assertions.assertFalse(tol.test(1.5, 1.51));
35          Assertions.assertFalse(tol.test(1.5, 1.50001));
36          Assertions.assertFalse(tol.test(1.5, Math.nextDown(1.5)));
37          Assertions.assertFalse(tol.test(0.0, -0.0));
38          Assertions.assertFalse(tol.test(0.0, Double.MIN_VALUE));
39          Assertions.assertFalse(tol.test(-Double.MIN_VALUE, Double.MIN_VALUE));
40          Assertions.assertTrue(tol.test(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY));
41          Assertions.assertFalse(tol.test(Double.MAX_VALUE, Double.POSITIVE_INFINITY));
42          Assertions.assertFalse(tol.test(Double.MAX_VALUE, Math.nextDown(Double.MAX_VALUE)));
43          Assertions.assertFalse(tol.test(1.0, Double.POSITIVE_INFINITY));
44          Assertions.assertTrue(tol.test(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY));
45          Assertions.assertFalse(tol.test(-Double.MAX_VALUE, Double.NEGATIVE_INFINITY));
46          Assertions.assertFalse(tol.test(-1.0, Double.NEGATIVE_INFINITY));
47          Assertions.assertTrue(tol.test(Double.NaN, Double.NaN));
48      }
49  
50      /**
51       * This should match {@link #testEquals()} with the exception that {@code -0.0 == 0.0}.
52       */
53      @Test
54      void testUlps0() {
55          final DoubleTolerance tol = DoubleTolerances.ulps(0);
56          Assertions.assertEquals("ulp=0", StringUtils.toString(tol));
57          // Standard tests
58          Assertions.assertTrue(tol.test(1.5, 1.5));
59          Assertions.assertFalse(tol.test(1.5, 1.51));
60          Assertions.assertFalse(tol.test(1.5, 1.50001));
61          Assertions.assertFalse(tol.test(1.5, Math.nextDown(1.5)));
62          // Allow zero equality
63          Assertions.assertTrue(tol.test(0.0, -0.0));
64          Assertions.assertFalse(tol.test(0.0, Double.MIN_VALUE));
65          Assertions.assertFalse(tol.test(-Double.MIN_VALUE, Double.MIN_VALUE));
66          Assertions.assertTrue(tol.test(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY));
67          Assertions.assertFalse(tol.test(Double.MAX_VALUE, Double.POSITIVE_INFINITY));
68          Assertions.assertFalse(tol.test(Double.MAX_VALUE, Math.nextDown(Double.MAX_VALUE)));
69          Assertions.assertFalse(tol.test(1.0, Double.POSITIVE_INFINITY));
70          Assertions.assertTrue(tol.test(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY));
71          Assertions.assertFalse(tol.test(-Double.MAX_VALUE, Double.NEGATIVE_INFINITY));
72          Assertions.assertFalse(tol.test(-1.0, Double.NEGATIVE_INFINITY));
73          Assertions.assertTrue(tol.test(Double.NaN, Double.NaN));
74      }
75  
76      @Test
77      void testUlps1() {
78          final DoubleTolerance tol = DoubleTolerances.ulps(1);
79          Assertions.assertEquals("ulp=1", StringUtils.toString(tol));
80          // Standard tests
81          Assertions.assertTrue(tol.test(1.5, 1.5));
82          Assertions.assertFalse(tol.test(1.5, 1.51));
83          Assertions.assertFalse(tol.test(1.5, 1.501));
84          Assertions.assertFalse(tol.test(15, 15.01));
85          Assertions.assertTrue(tol.test(1.5, Math.nextDown(1.5)));
86          Assertions.assertTrue(tol.test(0.0, -0.0));
87          Assertions.assertTrue(tol.test(0.0, Double.MIN_VALUE));
88          Assertions.assertFalse(tol.test(-Double.MIN_VALUE, Double.MIN_VALUE));
89          Assertions.assertTrue(tol.test(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY));
90          Assertions.assertTrue(tol.test(Double.MAX_VALUE, Double.POSITIVE_INFINITY));
91          Assertions.assertTrue(tol.test(Double.MAX_VALUE, Math.nextDown(Double.MAX_VALUE)));
92          Assertions.assertFalse(tol.test(Double.MAX_VALUE, Math.nextDown(Math.nextDown(Double.MAX_VALUE))));
93          Assertions.assertFalse(tol.test(1.0, Double.POSITIVE_INFINITY));
94          Assertions.assertTrue(tol.test(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY));
95          Assertions.assertTrue(tol.test(-Double.MAX_VALUE, Double.NEGATIVE_INFINITY));
96          Assertions.assertFalse(tol.test(-1.0, Double.NEGATIVE_INFINITY));
97          Assertions.assertTrue(tol.test(Double.NaN, Double.NaN));
98      }
99  
100     @Test
101     void testAbsolute() {
102         final double eps = 1e-3;
103         final DoubleTolerance tol = DoubleTolerances.absolute(eps);
104         Assertions.assertEquals("abs=" + eps, StringUtils.toString(tol));
105         // Standard tests
106         Assertions.assertTrue(tol.test(1.5, 1.5));
107         Assertions.assertFalse(tol.test(1.5, 1.51));
108         Assertions.assertTrue(tol.test(1.5, 1.501));
109         // Not equal absolutely
110         Assertions.assertFalse(tol.test(15, 15.01));
111         Assertions.assertTrue(tol.test(1.5, Math.nextDown(1.5)));
112         Assertions.assertTrue(tol.test(0.0, -0.0));
113         Assertions.assertTrue(tol.test(0.0, Double.MIN_VALUE));
114         Assertions.assertTrue(tol.test(-Double.MIN_VALUE, Double.MIN_VALUE));
115         Assertions.assertTrue(tol.test(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY));
116         Assertions.assertTrue(tol.test(Double.MAX_VALUE, Double.POSITIVE_INFINITY));
117         // 1 ULP is OK
118         Assertions.assertTrue(tol.test(Double.MAX_VALUE, Math.nextDown(Double.MAX_VALUE)));
119         // Not equal as absolute difference is large
120         Assertions.assertFalse(tol.test(Double.MAX_VALUE, Math.nextDown(Math.nextDown(Double.MAX_VALUE))));
121         Assertions.assertFalse(tol.test(1.0, Double.POSITIVE_INFINITY));
122         Assertions.assertTrue(tol.test(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY));
123         Assertions.assertTrue(tol.test(-Double.MAX_VALUE, Double.NEGATIVE_INFINITY));
124         Assertions.assertFalse(tol.test(-1.0, Double.NEGATIVE_INFINITY));
125         Assertions.assertTrue(tol.test(Double.NaN, Double.NaN));
126     }
127 
128     @Test
129     void testRelative() {
130         final double eps = 1e-3;
131         final DoubleTolerance tol = DoubleTolerances.relative(eps);
132         Assertions.assertEquals("rel=" + eps, StringUtils.toString(tol));
133         // Standard tests
134         Assertions.assertTrue(tol.test(1.5, 1.5));
135         Assertions.assertFalse(tol.test(1.5, 1.51));
136         Assertions.assertTrue(tol.test(1.5, 1.501));
137         // Equal relatively
138         Assertions.assertTrue(tol.test(0.15, 0.1501));
139         Assertions.assertTrue(tol.test(1.5, Math.nextDown(1.5)));
140         Assertions.assertTrue(tol.test(0.0, -0.0));
141         Assertions.assertTrue(tol.test(0.0, Double.MIN_VALUE));
142         // Not equals relatively: due to the sign change the relative difference is big
143         Assertions.assertFalse(tol.test(-Double.MIN_VALUE, Double.MIN_VALUE));
144         Assertions.assertTrue(tol.test(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY));
145         Assertions.assertTrue(tol.test(Double.MAX_VALUE, Double.POSITIVE_INFINITY));
146         // Equal as relative difference is small
147         Assertions.assertTrue(tol.test(Double.MAX_VALUE, Math.nextDown(Double.MAX_VALUE)));
148         Assertions.assertTrue(tol.test(Double.MAX_VALUE, Math.nextDown(Math.nextDown(Double.MAX_VALUE))));
149         Assertions.assertFalse(tol.test(1.0, Double.POSITIVE_INFINITY));
150         Assertions.assertTrue(tol.test(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY));
151         Assertions.assertTrue(tol.test(-Double.MAX_VALUE, Double.NEGATIVE_INFINITY));
152         Assertions.assertFalse(tol.test(-1.0, Double.NEGATIVE_INFINITY));
153         // Relative error does not check NaN are equal
154         Assertions.assertFalse(tol.test(Double.NaN, Double.NaN));
155     }
156 
157     @Test
158     void testMessageFormatting() {
159         final DoubleTolerance t1 = DoubleTolerances.equals();
160         final DoubleTolerance t2 = DoubleTolerances.ulps(1);
161         final DoubleTolerance t3 = DoubleTolerances.absolute(1e-3);
162         final DoubleTolerance t4 = DoubleTolerances.relative(1e-4);
163         final String s1 = StringUtils.toString(t1);
164         final String s2 = StringUtils.toString(t2);
165         final String s3 = StringUtils.toString(t3);
166         final String s4 = StringUtils.toString(t4);
167 
168         // Combination of tolerance descriptions adds parentheses when operators are different.
169         // This could be made smarter using operator precedence but it is assumed
170         // that not many tolerance objects will be concatenated.
171         Assertions.assertEquals(s1 + " || " + s2, StringUtils.toString(t1.or(t2)));
172         Assertions.assertEquals(s2 + " || " + s1, StringUtils.toString(t2.or(t1)));
173         Assertions.assertEquals(s3 + " || " + s4, StringUtils.toString(t3.or(t4)));
174         Assertions.assertEquals(s3 + " && " + s4, StringUtils.toString(t3.and(t4)));
175         Assertions.assertEquals(s3 + " && " + s4 + " && " + s1,
176             StringUtils.toString(t3.and(t4).and(t1)));
177         Assertions.assertEquals(s3 + " || " + s4 + " || " + s1,
178             StringUtils.toString(t3.or(t4).or(t1)));
179         Assertions.assertEquals("(" + s3 + " && " + s4 + ") || " + s1,
180             StringUtils.toString(t3.and(t4).or(t1)));
181         Assertions.assertEquals("(" + s3 + " && " + s4 + ") || (" + s1 + " && " + s2 + ")",
182             StringUtils.toString(t3.and(t4).or(t1.and(t2))));
183         Assertions.assertEquals(s3 + " && (" + s1 + " || " + s2 + ")",
184             StringUtils.toString(t3.and(t1.or(t2))));
185 
186         // Negation
187         Assertions.assertEquals("!(" + s1 + ")", StringUtils.toString(t1.negate()));
188         Assertions.assertEquals(s1, StringUtils.toString(t1.negate().negate()));
189         Assertions.assertEquals(s1 + " || !(" + s2 + ")", StringUtils.toString(t1.or(t2.negate())));
190         Assertions.assertEquals("!(" + s1 + " || " + s2 + ")", StringUtils.toString(t1.or(t2).negate()));
191     }
192 
193     @Test
194     void testCombinations() {
195         final DoubleTolerance eq = DoubleTolerances.equals();
196         final DoubleTolerance ulp = DoubleTolerances.ulps(2);
197         final DoubleTolerance abs = DoubleTolerances.absolute(1e-3);
198         final DoubleTolerance rel = DoubleTolerances.relative(1e-6);
199 
200         // a,b are absolutely equal
201         // c,d are absolutely and ulp equal
202         // e,f are relatively equal
203         double a = 0.12345;
204         double b = 0.123456;
205         double c = Double.MIN_VALUE;
206         double d = -c;
207         double e = 12345.67;
208         double f = 12345.678;
209 
210         // Verify the test conditions
211         Assertions.assertTrue(eq.test(a, a));
212         Assertions.assertTrue(ulp.test(a, a));
213         Assertions.assertTrue(abs.test(a, a));
214         Assertions.assertTrue(abs.test(a, b));
215         Assertions.assertFalse(eq.negate().test(a, a));
216         Assertions.assertFalse(ulp.negate().test(a, a));
217         Assertions.assertFalse(abs.negate().test(a, a));
218         Assertions.assertFalse(abs.negate().test(a, b));
219 
220         Assertions.assertFalse(eq.test(a, b));
221         Assertions.assertFalse(ulp.test(a, b));
222         Assertions.assertTrue(abs.test(a, b), "absolute");
223         Assertions.assertFalse(rel.test(a, b));
224 
225         Assertions.assertTrue(eq.test(c, c));
226         Assertions.assertTrue(ulp.test(c, c));
227         Assertions.assertTrue(abs.test(c, c));
228         Assertions.assertTrue(abs.test(c, c));
229 
230         Assertions.assertFalse(eq.test(c, d));
231         Assertions.assertTrue(ulp.test(c, d), "ulp");
232         Assertions.assertTrue(abs.test(c, d), "absolute");
233         Assertions.assertFalse(rel.test(c, d));
234 
235         Assertions.assertTrue(eq.test(e, e));
236         Assertions.assertTrue(ulp.test(e, e));
237         Assertions.assertTrue(abs.test(e, e));
238         Assertions.assertTrue(abs.test(e, e));
239 
240         Assertions.assertFalse(eq.test(e, f));
241         Assertions.assertFalse(ulp.test(e, f));
242         Assertions.assertFalse(abs.test(e, f));
243         Assertions.assertTrue(rel.test(e, f), "relative");
244 
245 
246         // Test and/or
247         final DoubleTolerance[] tol = {eq, ulp, abs, rel};
248         final double[][] pairs = {{a, b}, {c, d}, {e, f}};
249         for (final double[] pair : pairs) {
250             final double x = pair[0];
251             final double y = pair[1];
252             for (final DoubleTolerance t1 : tol) {
253                 final boolean ex1 = t1.test(x, y);
254                 for (final DoubleTolerance t2 : tol) {
255                     final boolean ex2 = t2.test(x, y);
256 
257                     // Note:
258                     // We can use %s for the error message as the
259                     // custom DoubleTolerance overrides toString()
260                     Assertions.assertEquals(ex1 || ex2, t1.or(t2).test(x, y),
261                         () -> String.format("%s or %s test (%s, %s", t1, t2, x, y));
262                     Assertions.assertEquals(ex1 && ex2, t1.and(t2).test(x, y),
263                         () -> String.format("%s and %s test (%s, %s", t1, t2, x, y));
264 
265                     for (final DoubleTolerance t3 : tol) {
266                         final boolean ex3 = t3.test(x, y);
267                         Assertions.assertEquals((ex1 || ex2) && ex3, t1.or(t2).and(t3).test(x, y),
268                             () -> String.format("(%s or %s) and %s test (%s, %s", t1, t2, t3, x, y));
269                         Assertions.assertEquals((ex1 || ex2) || ex3, t1.or(t2).or(t3).test(x, y),
270                             () -> String.format("(%s or %s) or %s test (%s, %s", t1, t2, t3, x, y));
271                         Assertions.assertEquals((ex1 && ex2) && ex3, t1.and(t2).and(t3).test(x, y),
272                             () -> String.format("(%s and %s) and %s test (%s, %s", t1, t2, t3, x, y));
273                         Assertions.assertEquals((ex1 && ex2) || ex3, t1.and(t2).or(t3).test(x, y),
274                             () -> String.format("(%s and %s) or %s test (%s, %s", t1, t2, t3, x, y));
275 
276                         Assertions.assertEquals(ex1 || (ex2 && ex3), t1.or(t2.and(t3)).test(x, y),
277                             () -> String.format("%s or (%s and %s) test (%s, %s", t1, t2, t3, x, y));
278                         Assertions.assertEquals(ex1 || (ex2 || ex3), t1.or(t2.or(t3)).test(x, y),
279                             () -> String.format("%s or (%s or %s) test (%s, %s", t1, t2, t3, x, y));
280                         Assertions.assertEquals(ex1 && (ex2 && ex3), t1.and(t2.and(t3)).test(x, y),
281                             () -> String.format("%s and (%s and %s) test (%s, %s", t1, t2, t3, x, y));
282                         Assertions.assertEquals(ex1 && (ex2 || ex3), t1.and(t2.or(t3)).test(x, y),
283                             () -> String.format("%s and (%s or %s) test (%s, %s", t1, t2, t3, x, y));
284                     }
285                 }
286             }
287         }
288     }
289 
290     @Test
291     void testSymmetry() {
292         final int maxUlps = 4;
293         final double absTolerance = 1e-3;
294         final double relTolerance = 1e-6;
295         final DoubleTolerance eq = DoubleTolerances.equals();
296         final DoubleTolerance ulp = DoubleTolerances.ulps(maxUlps);
297         final DoubleTolerance abs = DoubleTolerances.absolute(absTolerance);
298         final DoubleTolerance rel = DoubleTolerances.relative(relTolerance);
299         final UniformRandomProvider rng = RandomSource.JSF_64.create();
300         final long one = Double.doubleToLongBits(1.0);
301         // Test a range of values that cover true/false cases for each tolerance
302         for (final int exp : new int[] {
303             // Include sub-normal and small numbers
304             -1074, -1073, -1066, -1022, -567, -123, -20,
305             // Realistic scales for probabilities: 1e-8 to 1e-1
306             -8, -7, -6, -5, -4, -3, -2, -1,
307             // Ranges > 1
308             0, 1, 2, 3, 4, 8, 156, 1022, 1023
309         }) {
310             final double scale = Math.scalb(1.0, exp);
311             for (int i = 0; i < 50; i++) {
312                 // A random number in [1, 2).
313                 // Combine the exponent from 1.0 with 52 bits in the mantissa
314                 final double u = Double.longBitsToDouble(one | (rng.nextLong() >>> 12));
315                 Assertions.assertTrue(u >= 1.0 && u < 2.0, "Unexpected range");
316                 // Scale
317                 final double x = u * scale;
318                 Assertions.assertTrue(x > 0 && x <= Double.MAX_VALUE, () -> "Unexpected scaled number: " + x);
319                 // Create variants that pass and fail each tolerance test.
320                 // Note: Failure of the absolute tolerance is dependent on the scale.
321                 final double dx1 = Math.ulp(x) * maxUlps;
322                 final double dx2 = absTolerance;
323                 final double dx3 = x * relTolerance;
324                 final double[] ys = {
325                     x,
326                     x + dx1 * 0.5, x + dx1, x + dx1 * 2, x - dx1 * 0.5, x - dx1, x - dx1 * 2,
327                     x + dx2 * 0.5, x + dx2, x + dx2 * 2, x - dx2 * 0.5, x - dx2, x - dx2 * 2,
328                     x + dx3 * 0.5, x + dx3, x + dx3 * 2, x - dx3 * 0.5, x - dx3, x - dx3 * 2,
329                 };
330                 for (final double y : ys) {
331                     Assertions.assertEquals(eq.test(x, y), eq.test(y, x), "eq");
332                     Assertions.assertEquals(ulp.test(x, y), ulp.test(y, x), "ulp");
333                     Assertions.assertEquals(abs.test(x, y), abs.test(y, x), "abs");
334                     Assertions.assertEquals(rel.test(x, y), rel.test(y, x), "rel");
335                     // Support negative values
336                     Assertions.assertEquals(eq.test(x, y), eq.test(-y, -x), "eq");
337                     Assertions.assertEquals(ulp.test(x, y), ulp.test(-y, -x), "ulp");
338                     Assertions.assertEquals(abs.test(x, y), abs.test(-y, -x), "abs");
339                     Assertions.assertEquals(rel.test(x, y), rel.test(-y, -x), "rel");
340                 }
341             }
342         }
343     }
344 }