Skip to content

Commit 24fa15d

Browse files
authored
Preserve half_float's precision in fields API (#70653)
This modifies the fields API to return values with half_float's precision. This makes the fields API better reflect what we've indexed which we'd like to to do in general. It does make the values that come back "uglier" because things like `3.14` end up becoming `3.140625`. But that is what is actually in the index so itsmore "real". Closes #70260
1 parent 184cb44 commit 24fa15d

File tree

4 files changed

+44
-10
lines changed

4 files changed

+44
-10
lines changed

server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,18 @@ public enum NumberType {
124124
HALF_FLOAT("half_float", NumericType.HALF_FLOAT) {
125125
@Override
126126
public Float parse(Object value, boolean coerce) {
127+
final float result = parseToFloat(value);
128+
// Reduce the precision to what we actually index
129+
return HalfFloatPoint.sortableShortToHalfFloat(HalfFloatPoint.halfFloatToSortableShort(result));
130+
}
131+
132+
/**
133+
* Parse a query parameter or {@code _source} value to a float,
134+
* keeping float precision. Used by queries which need more
135+
* precise control over their rounding behavior that
136+
* {@link #parse(Object, boolean)} provides.
137+
*/
138+
private float parseToFloat(Object value) {
127139
final float result;
128140

129141
if (value instanceof Number) {
@@ -152,7 +164,7 @@ public Float parse(XContentParser parser, boolean coerce) throws IOException {
152164

153165
@Override
154166
public Query termQuery(String field, Object value) {
155-
float v = parse(value, false);
167+
float v = parseToFloat(value);
156168
return HalfFloatPoint.newExactQuery(field, v);
157169
}
158170

@@ -161,7 +173,7 @@ public Query termsQuery(String field, Collection<?> values) {
161173
float[] v = new float[values.size()];
162174
int pos = 0;
163175
for (Object value: values) {
164-
v[pos++] = parse(value, false);
176+
v[pos++] = parseToFloat(value);
165177
}
166178
return HalfFloatPoint.newSetQuery(field, v);
167179
}
@@ -173,14 +185,14 @@ public Query rangeQuery(String field, Object lowerTerm, Object upperTerm,
173185
float l = Float.NEGATIVE_INFINITY;
174186
float u = Float.POSITIVE_INFINITY;
175187
if (lowerTerm != null) {
176-
l = parse(lowerTerm, false);
188+
l = parseToFloat(lowerTerm);
177189
if (includeLower) {
178190
l = HalfFloatPoint.nextDown(l);
179191
}
180192
l = HalfFloatPoint.nextUp(l);
181193
}
182194
if (upperTerm != null) {
183-
u = parse(upperTerm, false);
195+
u = parseToFloat(upperTerm);
184196
if (includeUpper) {
185197
u = HalfFloatPoint.nextUp(u);
186198
}

server/src/test/java/org/elasticsearch/index/mapper/NumberFieldTypeTests.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ public void testConversions() {
277277
assertEquals("Value [2147483648] is out of range for an integer", e.getMessage());
278278
e = expectThrows(IllegalArgumentException.class, () -> NumberType.LONG.parse(10000000000000000000d, true));
279279
assertEquals("Value [1.0E19] is out of range for a long", e.getMessage());
280-
assertEquals(1.1f, NumberType.HALF_FLOAT.parse(1.1, true));
280+
assertEquals(1.0996094f, NumberType.HALF_FLOAT.parse(1.1, true)); // Half float loses a bit of precision even on 1.1....
281281
assertEquals(1.1f, NumberType.FLOAT.parse(1.1, true));
282282
assertEquals(1.1d, NumberType.DOUBLE.parse(1.1, true));
283283
}
@@ -648,4 +648,22 @@ public void testFetchSourceValue() throws IOException {
648648
assertEquals(List.of(2.71f), fetchSourceValue(nullValueMapper, ""));
649649
assertEquals(List.of(2.71f), fetchSourceValue(nullValueMapper, null));
650650
}
651+
652+
public void testFetchHalfFloatFromSource() throws IOException {
653+
MappedFieldType mapper = new NumberFieldMapper.Builder("field", NumberType.HALF_FLOAT, false, true)
654+
.build(new ContentPath())
655+
.fieldType();
656+
/*
657+
* Half float loses a fair bit of precision compared to float but
658+
* we still do floating point comparisons. The "funny" trailing
659+
* {@code .000625} is, for example, the precision loss of using
660+
* a half float reflected back into a float.
661+
*/
662+
assertEquals(List.of(3.140625F), fetchSourceValue(mapper, 3.14));
663+
assertEquals(List.of(3.140625F), fetchSourceValue(mapper, 3.14F));
664+
assertEquals(List.of(3.0F), fetchSourceValue(mapper, 3));
665+
assertEquals(List.of(42.90625F), fetchSourceValue(mapper, "42.9"));
666+
assertEquals(List.of(47.125F), fetchSourceValue(mapper, 47.1231234));
667+
assertEquals(List.of(3.140625F, 42.90625F), fetchSourceValues(mapper, 3.14, "foo", "42.9"));
668+
}
651669
}

x-pack/plugin/sql/qa/mixed-node/src/test/java/org/elasticsearch/xpack/sql/qa/mixed_node/SqlSearchIT.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public void testAllTypesWithRequestToOldNodes() throws Exception {
9494
columns -> {
9595
columns.add(columnInfo("geo_point_field", "geo_point"));
9696
columns.add(columnInfo("float_field", "float"));
97-
columns.add(columnInfo("half_float_field", "half_float"));
97+
// Until #70653 is backported we won't assert that the half_float is returned with any kind of precision
9898
},
9999
(builder, fieldValues) -> {
100100
Float randomFloat = randomFloat();
@@ -114,7 +114,8 @@ public void testAllTypesWithRequestToOldNodes() throws Exception {
114114
fieldValues.put("geo_point_field", "POINT (-122.083843 37.386483)");
115115
builder.append("\"float_field\":" + randomFloat + ",");
116116
fieldValues.put("float_field", Double.valueOf(Float.valueOf(randomFloat).toString()));
117-
builder.append("\"half_float_field\":" + fieldValues.computeIfAbsent("half_float_field", v -> 123.456));
117+
builder.append("\"half_float_field\":\"123.456\"");
118+
// Until #70653 is backported we won't assert that the half_float is returned with any kind of precision
118119
}
119120
}
120121
);
@@ -126,7 +127,7 @@ public void testAllTypesWithRequestToUpgradedNodes() throws Exception {
126127
columns -> {
127128
columns.add(columnInfo("geo_point_field", "geo_point"));
128129
columns.add(columnInfo("float_field", "float"));
129-
columns.add(columnInfo("half_float_field", "half_float"));
130+
// Until #70653 is backported we won't assert that the half_float is returned with any kind of precision
130131
},
131132
(builder, fieldValues) -> {
132133
Float randomFloat = randomFloat();
@@ -143,7 +144,8 @@ public void testAllTypesWithRequestToUpgradedNodes() throws Exception {
143144
fieldValues.put("geo_point_field", "POINT (-122.083843 37.386483)");
144145
builder.append("\"float_field\":" + randomFloat + ",");
145146
fieldValues.put("float_field", Double.valueOf(Float.valueOf(randomFloat).toString()));
146-
builder.append("\"half_float_field\":" + fieldValues.computeIfAbsent("half_float_field", v -> 123.456));
147+
builder.append("\"half_float_field\":\"123.456\"");
148+
// Until #70653 is backported we won't assert that the half_float is returned with any kind of precision
147149
}
148150
}
149151
);

x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/FieldExtractorTestCase.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,9 @@ public void testCoerceForFloatingPointTypes() throws IOException {
229229

230230
// because "coerce" is true, a "123.456" floating point number STRING should be converted to 123.456 as number
231231
// and converted to 123.5 for "scaled_float" type
232-
expected.put("rows", singletonList(singletonList(isScaledFloat ? 123.5 : 123.456d)));
232+
// and 123.4375 for "half_float" because that is all it stores.
233+
double expectedNumber = isScaledFloat ? 123.5 : fieldType.equals("half_float") ? 123.4375 : 1234.56;
234+
expected.put("rows", singletonList(singletonList(expectedNumber)));
233235
assertResponse(expected, runSql("SELECT " + fieldType + "_field FROM test"));
234236
}
235237

0 commit comments

Comments
 (0)