Skip to content

Scale doubles to floats when necessary to match the field (#78344)#8

Open
MitchLewis930 wants to merge 1 commit intopr_018_beforefrom
pr_018_after
Open

Scale doubles to floats when necessary to match the field (#78344)#8
MitchLewis930 wants to merge 1 commit intopr_018_beforefrom
pr_018_after

Conversation

@MitchLewis930
Copy link

@MitchLewis930 MitchLewis930 commented Jan 29, 2026

PR_018

Summary by CodeRabbit

  • New Features

    • Added support for float and half_float field types in range aggregations.
  • Bug Fixes

    • Fixed range aggregation boundary calculations to align with stored numeric precision, ensuring accurate bucket assignments.
  • Tests

    • Added test coverage for range aggregations with exclusive endpoints across numeric field types.

)

This fixes a bug where the range aggregation always treats the range end points as doubles, even if the field value doesn't have enough resolution to fill a double. This was creating issues where the range would have a "more precise" approximation of an unrepresentable number than the field, causing the value to fall in the wrong bucket.

Note 1: This does not resolve the case where we have a long value that is not precisely representable as a double. Since the wire format sends the range bounds as doubles, by the time we get to where this fix is operating, we've already lost the precision to act on a big long. Fixing that problem will require a wire format change, and I'm not convinced it's worth it right now.

Note 2: This is probably still broken for ScaledFloats, since they don't implement NumberFieldType.

Resolves elastic#77033
@coderabbitai
Copy link

coderabbitai bot commented Jan 29, 2026

📝 Walkthrough

Walkthrough

The changes implement precision-correction logic for range aggregations to align double values with their stored precision across numeric field types. A new method in NumberFieldMapper reduces doubles to stored precision, which is then applied to range bounds during aggregation queries. New float and half_float field types are added with corresponding YAML test cases and unit tests for exclusive endpoint behavior.

Changes

Cohort / File(s) Summary
Numeric Precision Correction
server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java, server/src/main/java/org/elasticsearch/search/aggregations/support/ValuesSourceConfig.java, server/src/main/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregationBuilder.java
Introduces reduceToStoredPrecision() method to align double values with stored precision; adds reduceToStoredPrecisionFunction() getter in ValuesSourceConfig; applies precision fixing to range aggregation bounds before string-based overrides.
Range Aggregation Tests
rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.aggregation/40_range.yml, rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/msearch/20_typed_keys.yml
Adds float and half_float field mappings; updates range boundary assertions from 19.2499999 to 19.25; introduces exclusive endpoint test cases for new numeric field types.
Unit Tests
server/src/test/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregatorTests.java
Adds three new test methods (testDoubleRangesExclusiveEndpoint, testFloatRangesExclusiveEndpoint, testHalfFloatRangesExclusiveEndpoint) to verify exclusive endpoint behavior; updates existing test invocations with Consumer type casting.
Build Configuration
rest-api-spec/build.gradle
Adds skipTest entry for msearch typed_keys compatibility test in yamlRestTestV7CompatTransform.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 The rabbit's whiskers twitch with delight,
Double precision troubles now set right,
Float fields and half-floats find their way,
Range aggregations hop and play! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: fixing range aggregation precision by scaling doubles to match field storage precision (float/half_float types).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pr_018_after

Comment @coderabbitai help to get the list of available commands and usage tips.

@MitchLewis930
Copy link
Author

@CodeRabbit full review

@coderabbitai
Copy link

coderabbitai bot commented Jan 29, 2026

✅ Actions performed

Full review triggered.

@MitchLewis930
Copy link
Author

@coderabbitai full review

@coderabbitai
Copy link

coderabbitai bot commented Feb 12, 2026

✅ Actions performed

Full review triggered.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@server/src/main/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregationBuilder.java`:
- Line 27: RangeAggregationBuilder currently calls
DoubleUnaryOperator.applyAsDouble on Range.from/Range.to (and ignores string
overrides fromAsStr/toAsStr), which can NPE for null (unbounded) bounds and
skips precision adjustment for string-based inputs; update the code path that
applies precision so it null-checks before calling applyAsDouble and also
normalize/adjust precision when handling fromAsStr and toAsStr values
(convert/parse string bounds, apply the same DoubleUnaryOperator/precision
logic, and preserve null/unbounded semantics) so both numeric and string bounds
are handled null-safely and consistently; touch the logic around
RangeAggregationBuilder where range.from, range.to, fromAsStr, toAsStr and the
DoubleUnaryOperator are used to implement this.

In
`@server/src/main/java/org/elasticsearch/search/aggregations/support/ValuesSourceConfig.java`:
- Line 16: The current change applies reduceToStoredPrecisionFunction() to all
NumberFieldType instances causing integer range aggs to throw; restrict the
precision-reduction to only floating point field types by checking the field's
type (e.g., FloatFieldType / DoubleFieldType or
NumberFieldType.isFloatingPoint()) before wrapping with
reduceToStoredPrecisionFunction(), leaving integer NumberFieldType handling
unchanged (so reduceToStoredPrecision() with coerce=false is not applied to
integer fields). Update the logic in ValuesSourceConfig where
reduceToStoredPrecisionFunction() is attached (and the related branches around
lines referenced) to guard with a floating-type check so only floating types get
the precision adjustment.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d713c95 and b968fcb.

📒 Files selected for processing (7)
  • rest-api-spec/build.gradle
  • rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/msearch/20_typed_keys.yml
  • rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.aggregation/40_range.yml
  • server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java
  • server/src/main/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregationBuilder.java
  • server/src/main/java/org/elasticsearch/search/aggregations/support/ValuesSourceConfig.java
  • server/src/test/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregatorTests.java
🧰 Additional context used
🧬 Code graph analysis (1)
server/src/main/java/org/elasticsearch/search/aggregations/support/ValuesSourceConfig.java (1)
server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java (1)
  • NumberFieldMapper (65-1283)
🔇 Additional comments (7)
rest-api-spec/build.gradle (1)

85-86: LGTM!

The skip entry is appropriately added for V7 compatibility testing. Since the YAML test now uses the corrected boundary value (19.25 instead of the workaround 19.2499999), skipping this test in V7 compat mode is necessary because the old workaround behavior is no longer applicable.

rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/msearch/20_typed_keys.yml (1)

67-67: LGTM!

The boundary value change from 19.2499999 to 19.25 correctly reflects the fix for scaling doubles to floats. The previous workaround value was needed because range endpoints were treated as doubles even when the field was a float, causing precision mismatches. With the fix in place, the cleaner boundary value can now be used directly.

The test assertions remain correct:

  • Bucket *-19.25: captures 17.3349 and 19.0517 (2 docs)
  • Bucket 19.25-*: captures 19.3717 (1 doc)

Also applies to: 81-81

server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java (1)

1043-1056: Precision-reduction helper looks solid.

Clear intent and safe handling of infinities; aligns with the float/half-float casting behavior described in the PR.

rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.aggregation/40_range.yml (2)

14-33: Mappings and bulk data updates look good.

The added float and half_float fields plus sample values give the tests the right coverage basis.


38-93: Nice coverage for exclusive endpoints on float/half_float.

These cases validate the precision-fix behavior and mirror the existing double tests well.

server/src/test/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregatorTests.java (2)

20-20: Exclusive-endpoint tests provide solid coverage.

The new tests for DOUBLE/FLOAT/HALF_FLOAT directly validate the precision correction with boundary values. The NumericUtils usage is appropriate for the double case.

Also applies to: 103-164


237-237: Explicit Consumer casts are fine here.

They make type inference unambiguous without changing behavior.

Also applies to: 268-268, 302-302, 336-336, 368-368

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.function.DoubleUnaryOperator;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Null-safe precision fix + apply it to string bounds.

range.from / range.to can be null for unbounded ranges, so applyAsDouble will NPE. Also, string overrides (fromAsStr/toAsStr) bypass the precision fix entirely. Both can break correctness for unbounded ranges and string-based inputs.

✅ Proposed fix
-            Double from = fixPrecision.applyAsDouble(range.from);
-            Double to = fixPrecision.applyAsDouble(range.to);
+            Double from = range.from == null ? null : fixPrecision.applyAsDouble(range.from);
+            Double to = range.to == null ? null : fixPrecision.applyAsDouble(range.to);
             if (range.fromAsStr != null) {
-                from = parser.parseDouble(range.fromAsStr, false, context::nowInMillis);
+                from = fixPrecision.applyAsDouble(parser.parseDouble(range.fromAsStr, false, context::nowInMillis));
             }
             if (range.toAsStr != null) {
-                to = parser.parseDouble(range.toAsStr, false, context::nowInMillis);
+                to = fixPrecision.applyAsDouble(parser.parseDouble(range.toAsStr, false, context::nowInMillis));
             }

Also applies to: 156-175

🤖 Prompt for AI Agents
In
`@server/src/main/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregationBuilder.java`
at line 27, RangeAggregationBuilder currently calls
DoubleUnaryOperator.applyAsDouble on Range.from/Range.to (and ignores string
overrides fromAsStr/toAsStr), which can NPE for null (unbounded) bounds and
skips precision adjustment for string-based inputs; update the code path that
applies precision so it null-checks before calling applyAsDouble and also
normalize/adjust precision when handling fromAsStr and toAsStr values
(convert/parse string bounds, apply the same DoubleUnaryOperator/precision
logic, and preserve null/unbounded semantics) so both numeric and string bounds
are handled null-safely and consistently; touch the logic around
RangeAggregationBuilder where range.from, range.to, fromAsStr, toAsStr and the
DoubleUnaryOperator are used to implement this.

import org.elasticsearch.index.fielddata.IndexGeoPointFieldData;
import org.elasticsearch.index.fielddata.IndexNumericFieldData;
import org.elasticsearch.index.mapper.MappedFieldType;
import org.elasticsearch.index.mapper.NumberFieldMapper;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard precision reduction to floating types to avoid integer-range regressions.

reduceToStoredPrecisionFunction() applies to all NumberFieldTypes. For integer fields, reduceToStoredPrecision() parses with coerce=false, which will throw for decimal range bounds (previously accepted and rounded in range logic). Consider applying the precision fix only for floating types to avoid behavior changes on integer range aggs.

💡 Suggested fix
-    public DoubleUnaryOperator reduceToStoredPrecisionFunction() {
-        if (fieldContext() != null && fieldType() instanceof NumberFieldMapper.NumberFieldType) {
-            return ((NumberFieldMapper.NumberFieldType) fieldType())::reduceToStoredPrecision;
-        }
-        return (value) -> value;
-    }
+    public DoubleUnaryOperator reduceToStoredPrecisionFunction() {
+        if (fieldContext() != null && fieldType() instanceof NumberFieldMapper.NumberFieldType) {
+            NumberFieldMapper.NumberFieldType nft = (NumberFieldMapper.NumberFieldType) fieldType();
+            IndexNumericFieldData.NumericType nt = nft.numericType();
+            if (nt == IndexNumericFieldData.NumericType.FLOAT
+                || nt == IndexNumericFieldData.NumericType.HALF_FLOAT
+                || nt == IndexNumericFieldData.NumericType.DOUBLE) {
+                return nft::reduceToStoredPrecision;
+            }
+        }
+        return DoubleUnaryOperator.identity();
+    }

Also applies to: 24-24, 326-335

🤖 Prompt for AI Agents
In
`@server/src/main/java/org/elasticsearch/search/aggregations/support/ValuesSourceConfig.java`
at line 16, The current change applies reduceToStoredPrecisionFunction() to all
NumberFieldType instances causing integer range aggs to throw; restrict the
precision-reduction to only floating point field types by checking the field's
type (e.g., FloatFieldType / DoubleFieldType or
NumberFieldType.isFloatingPoint()) before wrapping with
reduceToStoredPrecisionFunction(), leaving integer NumberFieldType handling
unchanged (so reduceToStoredPrecision() with coerce=false is not applied to
integer fields). Update the logic in ValuesSourceConfig where
reduceToStoredPrecisionFunction() is attached (and the related branches around
lines referenced) to guard with a floating-type check so only floating types get
the precision adjustment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants