diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index 2e2f9ab0861..912974da3fc 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -109,6 +109,10 @@ Bug Fixes there are too many merges running and one of the merges hits a tragic exception (Joey Echeverria via Mike McCandless) +* LUCENE-7594: Fixed point range queries on floating-point types to recommend + using helpers for exclusive bounds that are consistent with Double.compare. + (Adrien Grand, Dawid Weiss) + Improvements * LUCENE-6824: TermAutomatonQuery now rewrites to TermQuery, diff --git a/lucene/core/src/java/org/apache/lucene/document/DoublePoint.java b/lucene/core/src/java/org/apache/lucene/document/DoublePoint.java index 9a383a489c0..6547402a265 100644 --- a/lucene/core/src/java/org/apache/lucene/document/DoublePoint.java +++ b/lucene/core/src/java/org/apache/lucene/document/DoublePoint.java @@ -45,6 +45,32 @@ import org.apache.lucene.util.NumericUtils; */ public final class DoublePoint extends Field { + /** + * Return the least double that compares greater than {@code d} consistently + * with {@link Double#compare}. The only difference with + * {@link Math#nextUp(double)} is that this method returns {@code +0d} when + * the argument is {@code -0d}. + */ + public static double nextUp(double d) { + if (Double.doubleToLongBits(d) == 0x8000_0000_0000_0000L) { // -0d + return +0d; + } + return Math.nextUp(d); + } + + /** + * Return the greatest double that compares less than {@code d} consistently + * with {@link Double#compare}. The only difference with + * {@link Math#nextDown(double)} is that this method returns {@code -0d} when + * the argument is {@code +0d}. + */ + public static double nextDown(double d) { + if (Double.doubleToLongBits(d) == 0L) { // +0d + return -0f; + } + return Math.nextDown(d); + } + private static FieldType getType(int numDims) { FieldType type = new FieldType(); type.setDimensions(numDims, Double.BYTES); @@ -164,8 +190,8 @@ public final class DoublePoint extends Field { *

* You can have half-open ranges (which are in fact </≤ or >/≥ queries) * by setting {@code lowerValue = Double.NEGATIVE_INFINITY} or {@code upperValue = Double.POSITIVE_INFINITY}. - *

Ranges are inclusive. For exclusive ranges, pass {@code Math#nextUp(lowerValue)} - * or {@code Math.nextDown(upperValue)}. + *

Ranges are inclusive. For exclusive ranges, pass {@link #nextUp(double) nextUp(lowerValue)} + * or {@link #nextUp(double) nextDown(upperValue)}. *

* Range comparisons are consistent with {@link Double#compareTo(Double)}. * diff --git a/lucene/core/src/java/org/apache/lucene/document/FloatPoint.java b/lucene/core/src/java/org/apache/lucene/document/FloatPoint.java index 8d84269d2e1..0ec67fd746c 100644 --- a/lucene/core/src/java/org/apache/lucene/document/FloatPoint.java +++ b/lucene/core/src/java/org/apache/lucene/document/FloatPoint.java @@ -45,6 +45,32 @@ import org.apache.lucene.util.NumericUtils; */ public final class FloatPoint extends Field { + /** + * Return the least float that compares greater than {@code f} consistently + * with {@link Float#compare}. The only difference with + * {@link Math#nextUp(float)} is that this method returns {@code +0f} when + * the argument is {@code -0f}. + */ + public static float nextUp(float f) { + if (Float.floatToIntBits(f) == 0x8000_0000) { // -0f + return +0f; + } + return Math.nextUp(f); + } + + /** + * Return the greatest float that compares less than {@code f} consistently + * with {@link Float#compare}. The only difference with + * {@link Math#nextDown(float)} is that this method returns {@code -0f} when + * the argument is {@code +0f}. + */ + public static float nextDown(float f) { + if (Float.floatToIntBits(f) == 0) { // +0f + return -0f; + } + return Math.nextDown(f); + } + private static FieldType getType(int numDims) { FieldType type = new FieldType(); type.setDimensions(numDims, Float.BYTES); @@ -164,8 +190,8 @@ public final class FloatPoint extends Field { *

* You can have half-open ranges (which are in fact </≤ or >/≥ queries) * by setting {@code lowerValue = Float.NEGATIVE_INFINITY} or {@code upperValue = Float.POSITIVE_INFINITY}. - *

Ranges are inclusive. For exclusive ranges, pass {@code Math#nextUp(lowerValue)} - * or {@code Math.nextDown(upperValue)}. + *

Ranges are inclusive. For exclusive ranges, pass {@link #nextUp(float) nextUp(lowerValue)} + * or {@link #nextUp(float) nextDown(upperValue)}. *

* Range comparisons are consistent with {@link Float#compareTo(Float)}. * diff --git a/lucene/core/src/test/org/apache/lucene/search/TestPointQueries.java b/lucene/core/src/test/org/apache/lucene/search/TestPointQueries.java index 73b28139f9e..5c66478c981 100644 --- a/lucene/core/src/test/org/apache/lucene/search/TestPointQueries.java +++ b/lucene/core/src/test/org/apache/lucene/search/TestPointQueries.java @@ -2052,4 +2052,32 @@ public class TestPointQueries extends LuceneTestCase { }); assertEquals("lowerPoint has length=4 but upperPoint has different length=8", e.getMessage()); } + + public void testNextUp() { + assertTrue(Double.compare(0d, DoublePoint.nextUp(-0d)) == 0); + assertTrue(Double.compare(Double.MIN_VALUE, DoublePoint.nextUp(0d)) == 0); + assertTrue(Double.compare(Double.POSITIVE_INFINITY, DoublePoint.nextUp(Double.MAX_VALUE)) == 0); + assertTrue(Double.compare(Double.POSITIVE_INFINITY, DoublePoint.nextUp(Double.POSITIVE_INFINITY)) == 0); + assertTrue(Double.compare(-Double.MAX_VALUE, DoublePoint.nextUp(Double.NEGATIVE_INFINITY)) == 0); + + assertTrue(Float.compare(0f, FloatPoint.nextUp(-0f)) == 0); + assertTrue(Float.compare(Float.MIN_VALUE, FloatPoint.nextUp(0f)) == 0); + assertTrue(Float.compare(Float.POSITIVE_INFINITY, FloatPoint.nextUp(Float.MAX_VALUE)) == 0); + assertTrue(Float.compare(Float.POSITIVE_INFINITY, FloatPoint.nextUp(Float.POSITIVE_INFINITY)) == 0); + assertTrue(Float.compare(-Float.MAX_VALUE, FloatPoint.nextUp(Float.NEGATIVE_INFINITY)) == 0); + } + + public void testNextDown() { + assertTrue(Double.compare(-0d, DoublePoint.nextDown(0d)) == 0); + assertTrue(Double.compare(-Double.MIN_VALUE, DoublePoint.nextDown(-0d)) == 0); + assertTrue(Double.compare(Double.NEGATIVE_INFINITY, DoublePoint.nextDown(-Double.MAX_VALUE)) == 0); + assertTrue(Double.compare(Double.NEGATIVE_INFINITY, DoublePoint.nextDown(Double.NEGATIVE_INFINITY)) == 0); + assertTrue(Double.compare(Double.MAX_VALUE, DoublePoint.nextDown(Double.POSITIVE_INFINITY)) == 0); + + assertTrue(Float.compare(-0f, FloatPoint.nextDown(0f)) == 0); + assertTrue(Float.compare(-Float.MIN_VALUE, FloatPoint.nextDown(-0f)) == 0); + assertTrue(Float.compare(Float.NEGATIVE_INFINITY, FloatPoint.nextDown(-Float.MAX_VALUE)) == 0); + assertTrue(Float.compare(Float.NEGATIVE_INFINITY, FloatPoint.nextDown(Float.NEGATIVE_INFINITY)) == 0); + assertTrue(Float.compare(Float.MAX_VALUE, FloatPoint.nextDown(Float.POSITIVE_INFINITY)) == 0); + } } diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestHalfFloatPoint.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestHalfFloatPoint.java index a24d99279b3..0bcb3f8b844 100644 --- a/lucene/sandbox/src/test/org/apache/lucene/document/TestHalfFloatPoint.java +++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestHalfFloatPoint.java @@ -229,6 +229,7 @@ public class TestHalfFloatPoint extends LuceneTestCase { // values that cannot be exactly represented as a half float assertEquals(HalfFloatPoint.nextUp(0f), HalfFloatPoint.nextUp(Float.MIN_VALUE), 0f); assertEquals(Float.floatToIntBits(-0f), Float.floatToIntBits(HalfFloatPoint.nextUp(-Float.MIN_VALUE))); + assertEquals(Float.floatToIntBits(0f), Float.floatToIntBits(HalfFloatPoint.nextUp(-0f))); } public void testNextDown() { @@ -239,5 +240,6 @@ public class TestHalfFloatPoint extends LuceneTestCase { // values that cannot be exactly represented as a half float assertEquals(Float.floatToIntBits(0f), Float.floatToIntBits(HalfFloatPoint.nextDown(Float.MIN_VALUE))); assertEquals(HalfFloatPoint.nextDown(-0f), HalfFloatPoint.nextDown(-Float.MIN_VALUE), 0f); + assertEquals(Float.floatToIntBits(-0f), Float.floatToIntBits(HalfFloatPoint.nextDown(+0f))); } }