From aae8464ff3c8d2297f00c7bbb2d8fc941602cb54 Mon Sep 17 00:00:00 2001 From: PHJ2000 <105151063+PHJ2000@users.noreply.github.com> Date: Mon, 5 Oct 2026 21:35:20 +0900 Subject: [PATCH] Fix loss of precision in standard deviation --- .../function/numeric/StandardDeviation.java | 14 ++++++++------ .../function/NumericPathFunctionTest.java | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/StandardDeviation.java b/json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/StandardDeviation.java index 0a83d8a86..5e82a04ca 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/StandardDeviation.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/function/numeric/StandardDeviation.java @@ -6,19 +6,21 @@ * Created by mattg on 6/27/15. */ public class StandardDeviation extends AbstractAggregation { - private Double sumSq = 0d; - private Double sum = 0d; - private Double count = 0d; + private double mean = 0d; + private double sumOfSquaredDifferences = 0d; + private long count = 0; @Override protected void next(Number value) { - sum += value.doubleValue(); - sumSq += value.doubleValue() * value.doubleValue(); + double number = value.doubleValue(); count++; + double delta = number - mean; + mean += delta / count; + sumOfSquaredDifferences += delta * (number - mean); } @Override protected Number getValue() { - return Math.sqrt(sumSq/count - sum*sum/count/count); + return Math.sqrt(sumOfSquaredDifferences / count); } } diff --git a/json-path/src/test/java/com/jayway/jsonpath/internal/function/NumericPathFunctionTest.java b/json-path/src/test/java/com/jayway/jsonpath/internal/function/NumericPathFunctionTest.java index c271b9f2c..7152f18c0 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/internal/function/NumericPathFunctionTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/internal/function/NumericPathFunctionTest.java @@ -120,4 +120,23 @@ public void testStddevOfEmptyListNegative(Configuration conf) { // assertThat(using(conf).parse(NUMBER_SERIES).read("$.numbers.foo()")).isEqualTo(numberSeries); // } + + @ParameterizedTest + @MethodSource("configurations") + public void testStdDevWithLargeOffset(Configuration conf) { + Object result = com.jayway.jsonpath.JsonPath.using(conf) + .parse("[10000000000,10000000001,10000000002]").read("$.stddev()"); + Number value = (Number) conf.jsonProvider().unwrap(result); + assertEquals(Math.sqrt(2.0 / 3.0), value.doubleValue(), 1e-12); + } + + @ParameterizedTest + @MethodSource("configurations") + public void testStdDevOfIdenticalLargeValues(Configuration conf) { + Object result = com.jayway.jsonpath.JsonPath.using(conf) + .parse("[1e200,1e200]").read("$.stddev()"); + Number value = (Number) conf.jsonProvider().unwrap(result); + assertEquals(0.0, value.doubleValue(), 0.0); + } + }