diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/ExtendedStatsAggregator.java b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/ExtendedStatsAggregator.java index 1d383a2ae19..4774bec573e 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/ExtendedStatsAggregator.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/ExtendedStatsAggregator.java @@ -202,7 +202,8 @@ class ExtendedStatsAggregator extends NumericMetricsAggregator.MultiValue { private double variance(long owningBucketOrd) { double sum = sums.get(owningBucketOrd); long count = counts.get(owningBucketOrd); - return (sumOfSqrs.get(owningBucketOrd) - ((sum * sum) / count)) / count; + double variance = (sumOfSqrs.get(owningBucketOrd) - ((sum * sum) / count)) / count; + return variance < 0 ? 0 : variance; } @Override diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalExtendedStats.java b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalExtendedStats.java index 608fd1de435..26a244c8ddf 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalExtendedStats.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/InternalExtendedStats.java @@ -101,7 +101,8 @@ public class InternalExtendedStats extends InternalStats implements ExtendedStat @Override public double getVariance() { - return (sumOfSqrs - ((sum * sum) / count)) / count; + double variance = (sumOfSqrs - ((sum * sum) / count)) / count; + return variance < 0 ? 0 : variance; } @Override diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/ExtendedStatsAggregatorTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/ExtendedStatsAggregatorTests.java index ca26ba1b206..83713ff52af 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/ExtendedStatsAggregatorTests.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/ExtendedStatsAggregatorTests.java @@ -99,6 +99,34 @@ public class ExtendedStatsAggregatorTests extends AggregatorTestCase { ); } + /** + * Testcase for https://github.com/elastic/elasticsearch/issues/37303 + */ + public void testVarianceNonNegative() throws IOException { + MappedFieldType ft = + new NumberFieldMapper.NumberFieldType(NumberFieldMapper.NumberType.DOUBLE); + ft.setName("field"); + final ExtendedSimpleStatsAggregator expected = new ExtendedSimpleStatsAggregator(); + testCase(ft, + iw -> { + int numDocs = 3; + for (int i = 0; i < numDocs; i++) { + Document doc = new Document(); + double value = 49.95d; + long valueAsLong = NumericUtils.doubleToSortableLong(value); + doc.add(new SortedNumericDocValuesField("field", valueAsLong)); + expected.add(value); + iw.addDocument(doc); + } + }, + stats -> { + //since the value(49.95) is a constant, variance should be 0 + assertEquals(0.0d, stats.getVariance(), TOLERANCE); + assertEquals(0.0d, stats.getStdDeviation(), TOLERANCE); + } + ); + } + public void testRandomLongs() throws IOException { MappedFieldType ft = new NumberFieldMapper.NumberFieldType(NumberFieldMapper.NumberType.LONG); @@ -236,7 +264,8 @@ public class ExtendedStatsAggregatorTests extends AggregatorTestCase { } double variance() { - return (sumOfSqrs - ((sum * sum) / count)) / count; + double variance = (sumOfSqrs - ((sum * sum) / count)) / count; + return variance < 0 ? 0 : variance; } } } diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/ExtendedStatsIT.java b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/ExtendedStatsIT.java index 4aa16d6f1d5..bdf67817496 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/ExtendedStatsIT.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/ExtendedStatsIT.java @@ -73,7 +73,8 @@ public class ExtendedStatsIT extends AbstractNumericTestCase { sum += val; sumOfSqrs += val * val; } - return (sumOfSqrs - ((sum * sum) / vals.length)) / vals.length; + double variance = (sumOfSqrs - ((sum * sum) / vals.length)) / vals.length; + return variance < 0 ? 0 : variance; } @Override diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/pipeline/ExtendedStatsBucketIT.java b/server/src/test/java/org/elasticsearch/search/aggregations/pipeline/ExtendedStatsBucketIT.java index a8ebf687ad6..9155947e3b6 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/pipeline/ExtendedStatsBucketIT.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/pipeline/ExtendedStatsBucketIT.java @@ -137,6 +137,7 @@ public class ExtendedStatsBucketIT extends ESIntegTestCase { double sumOfSqrs = 1.0 + 1.0 + 1.0 + 4.0 + 0.0 + 1.0; double avg = sum / count; double var = (sumOfSqrs - ((sum * sum) / count)) / count; + var = var < 0 ? 0 : var; double stdDev = Math.sqrt(var); assertThat(extendedStatsBucketValue, notNullValue()); assertThat(extendedStatsBucketValue.getName(), equalTo("extended_stats_bucket"));