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    *      http://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.geometry.core.precision;
18  
19  import org.apache.commons.geometry.core.GeometryTestUtils;
20  import org.junit.Assert;
21  import org.junit.Test;
22  
23  public class EpsilonDoublePrecisionContextTest {
24  
25      @Test
26      public void testGetters() {
27          // arrange
28          final double eps = 1e-6;
29  
30          // act
31          final EpsilonDoublePrecisionContext ctx = new EpsilonDoublePrecisionContext(eps);
32  
33          // assert
34          Assert.assertEquals(eps, ctx.getEpsilon(), 0.0);
35          Assert.assertEquals(eps, ctx.getMaxZero(), 0.0);
36      }
37  
38      @Test
39      public void testInvalidEpsilonValues() {
40          // act/assert
41          GeometryTestUtils.assertThrows(() -> {
42              new EpsilonDoublePrecisionContext(-1.0);
43          }, IllegalArgumentException.class);
44  
45          GeometryTestUtils.assertThrows(() -> {
46              new EpsilonDoublePrecisionContext(Double.NaN);
47          }, IllegalArgumentException.class, "Invalid epsilon value: NaN");
48  
49          GeometryTestUtils.assertThrows(() -> {
50              new EpsilonDoublePrecisionContext(Double.POSITIVE_INFINITY);
51          }, IllegalArgumentException.class, "Invalid epsilon value: Infinity");
52  
53          GeometryTestUtils.assertThrows(() -> {
54              new EpsilonDoublePrecisionContext(Double.NEGATIVE_INFINITY);
55          }, IllegalArgumentException.class, "Invalid epsilon value: -Infinity");
56      }
57  
58      @Test
59      public void testSign() {
60          // arrange
61          final double eps = 1e-2;
62  
63          final EpsilonDoublePrecisionContext ctx = new EpsilonDoublePrecisionContext(eps);
64  
65          // act/assert
66          Assert.assertEquals(0, ctx.sign(0.0));
67          Assert.assertEquals(0, ctx.sign(-0.0));
68  
69          Assert.assertEquals(0, ctx.sign(1e-2));
70          Assert.assertEquals(0, ctx.sign(-1e-2));
71  
72          Assert.assertEquals(1, ctx.sign(1e-1));
73          Assert.assertEquals(-1, ctx.sign(-1e-1));
74  
75          Assert.assertEquals(1, ctx.sign(Double.NaN));
76          Assert.assertEquals(1, ctx.sign(Double.POSITIVE_INFINITY));
77          Assert.assertEquals(-1, ctx.sign(Double.NEGATIVE_INFINITY));
78      }
79  
80      @Test
81      public void testCompare_compareToZero() {
82          // arrange
83          final double eps = 1e-2;
84  
85          final EpsilonDoublePrecisionContext ctx = new EpsilonDoublePrecisionContext(eps);
86  
87          // act/assert
88          Assert.assertEquals(0, ctx.compare(0.0, 0.0));
89          Assert.assertEquals(0, ctx.compare(+0.0, -0.0));
90          Assert.assertEquals(0, ctx.compare(eps, -0.0));
91          Assert.assertEquals(0, ctx.compare(+0.0, eps));
92  
93          Assert.assertEquals(0, ctx.compare(-eps, -0.0));
94          Assert.assertEquals(0, ctx.compare(+0.0, -eps));
95  
96          Assert.assertEquals(-1, ctx.compare(0.0, 1.0));
97          Assert.assertEquals(1, ctx.compare(1.0, 0.0));
98  
99          Assert.assertEquals(1, ctx.compare(0.0, -1.0));
100         Assert.assertEquals(-1, ctx.compare(-1.0, 0.0));
101     }
102 
103     @Test
104     public void testCompare_compareNonZero() {
105         // arrange
106         final double eps = 1e-5;
107         final double small = 1e-3;
108         final double big = 1e100;
109 
110         final EpsilonDoublePrecisionContext ctx = new EpsilonDoublePrecisionContext(eps);
111 
112         // act/assert
113         Assert.assertEquals(0, ctx.compare(eps, 2 * eps));
114         Assert.assertEquals(0, ctx.compare(-2 * eps, -eps));
115 
116         Assert.assertEquals(0, ctx.compare(small, small + (0.9 * eps)));
117         Assert.assertEquals(0, ctx.compare(-small - (0.9 * eps), -small));
118 
119         Assert.assertEquals(0, ctx.compare(big, nextUp(big, 1)));
120         Assert.assertEquals(0, ctx.compare(nextDown(-big, 1), -big));
121 
122         Assert.assertEquals(-1, ctx.compare(small, small + (1.1 * eps)));
123         Assert.assertEquals(1, ctx.compare(-small, -small - (1.1 * eps)));
124 
125         Assert.assertEquals(-1, ctx.compare(big, nextUp(big, 2)));
126         Assert.assertEquals(1, ctx.compare(-big, nextDown(-big, 2)));
127     }
128 
129     @Test
130     public void testCompare_NaN() {
131         // arrange
132         final EpsilonDoublePrecisionContext ctx = new EpsilonDoublePrecisionContext(1e-6);
133 
134         // act/assert
135         Assert.assertEquals(1, ctx.compare(0, Double.NaN));
136         Assert.assertEquals(1, ctx.compare(Double.NaN, 0));
137         Assert.assertEquals(1, ctx.compare(Double.NaN, Double.NaN));
138 
139         Assert.assertEquals(1, ctx.compare(Double.POSITIVE_INFINITY, Double.NaN));
140         Assert.assertEquals(1, ctx.compare(Double.NaN, Double.POSITIVE_INFINITY));
141 
142         Assert.assertEquals(1, ctx.compare(Double.NEGATIVE_INFINITY, Double.NaN));
143         Assert.assertEquals(1, ctx.compare(Double.NaN, Double.NEGATIVE_INFINITY));
144     }
145 
146     @Test
147     public void testCompare_infinity() {
148         // arrange
149         final EpsilonDoublePrecisionContext ctx = new EpsilonDoublePrecisionContext(1e-6);
150 
151         // act/assert
152         Assert.assertEquals(-1, ctx.compare(0, Double.POSITIVE_INFINITY));
153         Assert.assertEquals(1, ctx.compare(Double.POSITIVE_INFINITY, 0));
154         Assert.assertEquals(0, ctx.compare(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY));
155 
156         Assert.assertEquals(1, ctx.compare(0, Double.NEGATIVE_INFINITY));
157         Assert.assertEquals(-1, ctx.compare(Double.NEGATIVE_INFINITY, 0));
158         Assert.assertEquals(0, ctx.compare(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY));
159     }
160 
161     @Test
162     public void testGetMaxZero_isZeroEqualityThreshold() {
163         // arrange
164         final double eps = 1e-2;
165 
166         final EpsilonDoublePrecisionContext ctx = new EpsilonDoublePrecisionContext(eps);
167 
168         final double maxZero = ctx.getMaxZero();
169 
170         // act/assert
171         Assert.assertTrue(ctx.eqZero(maxZero));
172         Assert.assertTrue(ctx.eqZero(nextDown(maxZero, 1)));
173         Assert.assertFalse(ctx.eqZero(nextUp(maxZero, 1)));
174 
175         Assert.assertTrue(ctx.eqZero(-maxZero));
176         Assert.assertTrue(ctx.eqZero(nextUp(-maxZero, 1)));
177         Assert.assertFalse(ctx.eqZero(nextDown(-maxZero, 1)));
178     }
179 
180     @Test
181     public void testHashCode() {
182         // arrange
183         final EpsilonDoublePrecisionContext a = new EpsilonDoublePrecisionContext(1e-6);
184         final EpsilonDoublePrecisionContext b = new EpsilonDoublePrecisionContext(1e-7);
185         final EpsilonDoublePrecisionContext c = new EpsilonDoublePrecisionContext(1e-6);
186 
187         // act/assert
188         Assert.assertEquals(a.hashCode(), a.hashCode());
189         Assert.assertEquals(a.hashCode(), c.hashCode());
190 
191         Assert.assertNotEquals(a.hashCode(), b.hashCode());
192     }
193 
194     @Test
195     public void testEquals() {
196         // arrange
197         final EpsilonDoublePrecisionContext a = new EpsilonDoublePrecisionContext(1e-6);
198         final EpsilonDoublePrecisionContext b = new EpsilonDoublePrecisionContext(1e-7);
199         final EpsilonDoublePrecisionContext c = new EpsilonDoublePrecisionContext(1e-6);
200 
201         // act/assert
202         Assert.assertFalse(a.equals(null));
203         Assert.assertFalse(a.equals(new Object()));
204         Assert.assertNotEquals(a, b);
205         Assert.assertNotEquals(b, a);
206 
207         Assert.assertEquals(a, a);
208         Assert.assertEquals(a, c);
209     }
210 
211     @Test
212     public void testEqualsAndHashCode_signedZeroConsistency() {
213         // arrange
214         final EpsilonDoublePrecisionContext a = new EpsilonDoublePrecisionContext(0.0);
215         final EpsilonDoublePrecisionContext b = new EpsilonDoublePrecisionContext(-0.0);
216         final EpsilonDoublePrecisionContext c = new EpsilonDoublePrecisionContext(0.0);
217         final EpsilonDoublePrecisionContext d = new EpsilonDoublePrecisionContext(-0.0);
218 
219         // act/assert
220         Assert.assertFalse(a.equals(b));
221         Assert.assertNotEquals(a.hashCode(), b.hashCode());
222 
223         Assert.assertTrue(a.equals(c));
224         Assert.assertEquals(a.hashCode(), c.hashCode());
225 
226         Assert.assertTrue(b.equals(d));
227         Assert.assertEquals(b.hashCode(), d.hashCode());
228     }
229 
230     @Test
231     public void testToString() {
232         // arrange
233         final EpsilonDoublePrecisionContext a = new EpsilonDoublePrecisionContext(1d);
234 
235         // act
236         final String str = a.toString();
237 
238         // assert
239         Assert.assertTrue(str.contains("EpsilonDoublePrecisionContext"));
240         Assert.assertTrue(str.contains("epsilon= 1"));
241     }
242 
243     /**
244      * Increments the given double value {@code count} number of times
245      * using {@link Math#nextUp(double)}.
246      * @param n
247      * @param count
248      * @return
249      */
250     private static double nextUp(final double n, final int count) {
251         double result = n;
252         for (int i = 0; i < count; ++i) {
253             result = Math.nextUp(result);
254         }
255 
256         return result;
257     }
258 
259     /**
260      * Decrements the given double value {@code count} number of times
261      * using {@link Math#nextDown(double)}.
262      * @param n
263      * @param count
264      * @return
265      */
266     private static double nextDown(final double n, final int count) {
267         double result = n;
268         for (int i = 0; i < count; ++i) {
269             result = Math.nextDown(result);
270         }
271 
272         return result;
273     }
274 }