|
42 | 42 |
|
43 | 43 | public class MatcherUtils { |
44 | 44 |
|
| 45 | + /** Absolute tolerance floor for {@link #closeTo} numeric comparisons. */ |
| 46 | + private static final double ABSOLUTE_TOLERANCE = 1e-10; |
| 47 | + |
| 48 | + /** Number of ULPs tolerated by {@link #closeTo} to absorb platform-dependent rounding. */ |
| 49 | + private static final int ULP_TOLERANCE_FACTOR = 4; |
| 50 | + |
45 | 51 | private static final Logger LOG = LogManager.getLogger(); |
46 | 52 | private static final ObjectMapper JSON_MAPPER = new ObjectMapper(); |
47 | 53 |
|
@@ -302,29 +308,44 @@ protected boolean matchesSafely(JSONArray array) { |
302 | 308 | } |
303 | 309 |
|
304 | 310 | public static TypeSafeMatcher<JSONArray> closeTo(Object... values) { |
305 | | - final double error = 1e-10; |
306 | 311 | return new TypeSafeMatcher<JSONArray>() { |
307 | 312 | @Override |
308 | 313 | protected boolean matchesSafely(JSONArray item) { |
309 | 314 | List<Object> expectedValues = new ArrayList<>(Arrays.asList(values)); |
310 | 315 | List<Object> actualValues = new ArrayList<>(); |
311 | 316 | item.iterator().forEachRemaining(v -> actualValues.add((Object) v)); |
312 | | - return actualValues.stream() |
313 | | - .allMatch( |
314 | | - v -> |
315 | | - v instanceof Number |
316 | | - ? valuesAreClose( |
317 | | - (Number) v, (Number) expectedValues.get(actualValues.indexOf(v))) |
318 | | - : v.equals(expectedValues.get(actualValues.indexOf(v)))); |
| 317 | + if (actualValues.size() != expectedValues.size()) { |
| 318 | + return false; |
| 319 | + } |
| 320 | + for (int i = 0; i < actualValues.size(); i++) { |
| 321 | + Object actual = actualValues.get(i); |
| 322 | + Object expected = expectedValues.get(i); |
| 323 | + if (actual instanceof Number && expected instanceof Number) { |
| 324 | + if (!valuesAreClose((Number) actual, (Number) expected)) { |
| 325 | + return false; |
| 326 | + } |
| 327 | + } else if (!actual.equals(expected)) { |
| 328 | + return false; |
| 329 | + } |
| 330 | + } |
| 331 | + return true; |
319 | 332 | } |
320 | 333 |
|
321 | 334 | @Override |
322 | 335 | public void describeTo(Description description) { |
323 | 336 | description.appendText(Arrays.toString(values)); |
324 | 337 | } |
325 | 338 |
|
| 339 | + /** |
| 340 | + * ULP-aware comparison: tolerates up to {@link #ULP_TOLERANCE_FACTOR} ULPs or {@link |
| 341 | + * #ABSOLUTE_TOLERANCE}, whichever is larger. |
| 342 | + */ |
326 | 343 | private boolean valuesAreClose(Number v1, Number v2) { |
327 | | - return Math.abs(v1.doubleValue() - v2.doubleValue()) <= error; |
| 344 | + double d1 = v1.doubleValue(); |
| 345 | + double d2 = v2.doubleValue(); |
| 346 | + double diff = Math.abs(d1 - d2); |
| 347 | + double ulpTolerance = ULP_TOLERANCE_FACTOR * Math.max(Math.ulp(d1), Math.ulp(d2)); |
| 348 | + return diff <= Math.max(ABSOLUTE_TOLERANCE, ulpTolerance); |
328 | 349 | } |
329 | 350 | }; |
330 | 351 | } |
|
0 commit comments