-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add unit-of-least-precision float comparison #13723
Conversation
|
||
// Avoid possible overflow from adding the deltas by splitting the comparison | ||
assertTrue(deltaPlus <= maxUlps); | ||
assertTrue(deltaMinus <= (maxUlps - deltaPlus)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should add a return inside this if conditional. Otherwise you fall through to a condition check that may return an incorrect result if xInt - yInt
overflows.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course - my mistake! Thanks for pointing it out.
Should we add the "double" variant, too? |
@@ -654,7 +654,7 @@ private void assertFloatFacetResultsEqual(List<FacetResult> expected, List<Facet | |||
|
|||
assertEquals(expectedResult.dim, actualResult.dim); | |||
assertArrayEquals(expectedResult.path, actualResult.path); | |||
assertEquals((float) expectedResult.value, (float) actualResult.value, 2e-1); | |||
assertUlpEquals((float) expectedResult.value, (float) actualResult.value, (short) 2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is 2 ulps correct here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question @uschindler -- now we have the fun problem of figuring out how many ULPs can be lost due to different order of operations ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's correct in the sense that it's the smallest value that will pass the test that was failing. Is it the smallest value that will pass this test on any seed? That I'm not sure. I've run 500 iterations and didn't see the test fail again.
Can we have a test for the assertion method? |
+1, maybe as follow-on. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you @stefanvodita!
lucene/CHANGES.txt
Outdated
@@ -422,7 +422,9 @@ Build | |||
|
|||
Other | |||
-------------------- | |||
(No changes) | |||
|
|||
* GITHUB#13720: Add float comparison based on unit of least precision and use it to stop test failures because caused by |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove because
?
@@ -654,7 +654,7 @@ private void assertFloatFacetResultsEqual(List<FacetResult> expected, List<Facet | |||
|
|||
assertEquals(expectedResult.dim, actualResult.dim); | |||
assertArrayEquals(expectedResult.path, actualResult.path); | |||
assertEquals((float) expectedResult.value, (float) actualResult.value, 2e-1); | |||
assertUlpEquals((float) expectedResult.value, (float) actualResult.value, (short) 2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question @uschindler -- now we have the fun problem of figuring out how many ULPs can be lost due to different order of operations ...
Thank you for the feedback! I've added a comparison method for doubles and a test. |
I don't like the last commit because it changes from a assert-like method to a boolean returning method. Could we not keep the previous method signature and still add a test? |
I changed it away from an assertion because I liked this more. It makes it so you can assert on floats not being equal or use their equality in a condition, without making an assertion statement much longer. Do you not like it because it's too generic? We could move it to |
Maybe we could do both? I.e. add the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @stefanvodita! It's wild how complicated floating point numbers are for computers yet how tantalizingly simple our modern programming languages make them seem.
|
||
// Test signed zeros | ||
assertTrue(doubleEquals(0.0d, -0.0d, 0)); | ||
assertTrue(floatEquals(0.0f, -0.0f, (short) 0)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fun.
|
||
// Test NaNs | ||
assertFalse(doubleEquals(Double.NaN, Double.NaN, Integer.MAX_VALUE)); | ||
assertFalse(floatEquals(Float.NaN, Float.NaN, (short) 32767)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could maybe also use Math.nextAfter
to add one or two ulps to a random float/double and then assert
that float/doubleEquals
agrees?
@@ -422,7 +422,9 @@ Build | |||
|
|||
Other | |||
-------------------- | |||
(No changes) | |||
|
|||
* GITHUB#13720: Add float comparison based on unit of least precision and use it to stop test failures caused by float |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add IEEE 754 float summation implemented by Java not being commutative
or so?
Mathematically float summation is fine :)
That was my intention. |
I've moved the methods around and, as I was writing more tests, realised I'm not going to be as comprehensive as the originals tests, so I adapted those instead. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @stefanvodita!
lucene/CHANGES.txt
Outdated
(No changes) | ||
|
||
* GITHUB#13720: Add float comparison based on unit of least precision and use it to stop test failures caused by float | ||
summation not being commutative in Java's IEEE 754 implementation. (Alex Herbert, Stefan Vodita) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is always not commutative regardless of implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that I think about it, it's not even a commutativity issue. It's an associativity issue.
a + b == b + a
But
(a + b) + c != a + (b + c)
I'll fix the entry.
@@ -864,6 +864,14 @@ public static void assumeNoException(String msg, Exception e) { | |||
RandomizedTest.assumeNoException(msg, e); | |||
} | |||
|
|||
public static void assertFloatUlpEquals(final float x, final float y, final short maxUlps) { | |||
assertTrue(TestUtil.floatUlpEquals(x, y, maxUlps)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add a message showing string of both values.
The problem with assert true is that you get no useful output.
This is why I wanted to have the assert methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, will do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks fine.
One place where we should add this is the comparison of Panama vector vs. scalar vector test. The multiplication and summations when using Panama are executed in different order (the reason why we need a hand optimized impl). The test uses a quote large epsilon. It is only unclear to ne how many ulps we need for a dot product! Number of dimensions or maybe only number of splits of dimensions?
Thank you Mike and Uwe! I opened a separate issue to replace various epsilon-based equality checks (#13789), since that could be a large enough task, and I'll merge this one. |
👍 |
Comparing floats with a fixed epsilon doesn't really work. We add comparison based on unit-of-lest-precision (ULP) and use it to fix a failing test.
Closes #13720