LUCENE-7594: Fixed point range queries on floating-point types to recommend using helpers for exclusive bounds that are consistent with Double.compare.

This commit is contained in:
Adrien Grand 2016-12-21 19:33:52 +01:00
parent 5020ea28bc
commit 18d53a43f7
5 changed files with 90 additions and 4 deletions

View File

@ -109,6 +109,10 @@ Bug Fixes
there are too many merges running and one of the merges hits a there are too many merges running and one of the merges hits a
tragic exception (Joey Echeverria via Mike McCandless) 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 Improvements
* LUCENE-6824: TermAutomatonQuery now rewrites to TermQuery, * LUCENE-6824: TermAutomatonQuery now rewrites to TermQuery,

View File

@ -45,6 +45,32 @@ import org.apache.lucene.util.NumericUtils;
*/ */
public final class DoublePoint extends Field { 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) { private static FieldType getType(int numDims) {
FieldType type = new FieldType(); FieldType type = new FieldType();
type.setDimensions(numDims, Double.BYTES); type.setDimensions(numDims, Double.BYTES);
@ -164,8 +190,8 @@ public final class DoublePoint extends Field {
* <p> * <p>
* You can have half-open ranges (which are in fact &lt;/&le; or &gt;/&ge; queries) * You can have half-open ranges (which are in fact &lt;/&le; or &gt;/&ge; queries)
* by setting {@code lowerValue = Double.NEGATIVE_INFINITY} or {@code upperValue = Double.POSITIVE_INFINITY}. * by setting {@code lowerValue = Double.NEGATIVE_INFINITY} or {@code upperValue = Double.POSITIVE_INFINITY}.
* <p> Ranges are inclusive. For exclusive ranges, pass {@code Math#nextUp(lowerValue)} * <p> Ranges are inclusive. For exclusive ranges, pass {@link #nextUp(double) nextUp(lowerValue)}
* or {@code Math.nextDown(upperValue)}. * or {@link #nextUp(double) nextDown(upperValue)}.
* <p> * <p>
* Range comparisons are consistent with {@link Double#compareTo(Double)}. * Range comparisons are consistent with {@link Double#compareTo(Double)}.
* *

View File

@ -45,6 +45,32 @@ import org.apache.lucene.util.NumericUtils;
*/ */
public final class FloatPoint extends Field { 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) { private static FieldType getType(int numDims) {
FieldType type = new FieldType(); FieldType type = new FieldType();
type.setDimensions(numDims, Float.BYTES); type.setDimensions(numDims, Float.BYTES);
@ -164,8 +190,8 @@ public final class FloatPoint extends Field {
* <p> * <p>
* You can have half-open ranges (which are in fact &lt;/&le; or &gt;/&ge; queries) * You can have half-open ranges (which are in fact &lt;/&le; or &gt;/&ge; queries)
* by setting {@code lowerValue = Float.NEGATIVE_INFINITY} or {@code upperValue = Float.POSITIVE_INFINITY}. * by setting {@code lowerValue = Float.NEGATIVE_INFINITY} or {@code upperValue = Float.POSITIVE_INFINITY}.
* <p> Ranges are inclusive. For exclusive ranges, pass {@code Math#nextUp(lowerValue)} * <p> Ranges are inclusive. For exclusive ranges, pass {@link #nextUp(float) nextUp(lowerValue)}
* or {@code Math.nextDown(upperValue)}. * or {@link #nextUp(float) nextDown(upperValue)}.
* <p> * <p>
* Range comparisons are consistent with {@link Float#compareTo(Float)}. * Range comparisons are consistent with {@link Float#compareTo(Float)}.
* *

View File

@ -2052,4 +2052,32 @@ public class TestPointQueries extends LuceneTestCase {
}); });
assertEquals("lowerPoint has length=4 but upperPoint has different length=8", e.getMessage()); 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);
}
} }

View File

@ -229,6 +229,7 @@ public class TestHalfFloatPoint extends LuceneTestCase {
// values that cannot be exactly represented as a half float // values that cannot be exactly represented as a half float
assertEquals(HalfFloatPoint.nextUp(0f), HalfFloatPoint.nextUp(Float.MIN_VALUE), 0f); 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(-Float.MIN_VALUE)));
assertEquals(Float.floatToIntBits(0f), Float.floatToIntBits(HalfFloatPoint.nextUp(-0f)));
} }
public void testNextDown() { public void testNextDown() {
@ -239,5 +240,6 @@ public class TestHalfFloatPoint extends LuceneTestCase {
// values that cannot be exactly represented as a half float // values that cannot be exactly represented as a half float
assertEquals(Float.floatToIntBits(0f), Float.floatToIntBits(HalfFloatPoint.nextDown(Float.MIN_VALUE))); assertEquals(Float.floatToIntBits(0f), Float.floatToIntBits(HalfFloatPoint.nextDown(Float.MIN_VALUE)));
assertEquals(HalfFloatPoint.nextDown(-0f), HalfFloatPoint.nextDown(-Float.MIN_VALUE), 0f); assertEquals(HalfFloatPoint.nextDown(-0f), HalfFloatPoint.nextDown(-Float.MIN_VALUE), 0f);
assertEquals(Float.floatToIntBits(-0f), Float.floatToIntBits(HalfFloatPoint.nextDown(+0f)));
} }
} }