double addition rounds after each operation, so regrouping the same finite inputs can change the computed sum.
Java floating-point sums: input order can change the result
Make aggregation requirements explicit
A very large reading can absorb a much smaller one. When the large positive and negative values cancel later, the absorbed unit does not return. The fixture sums the same three values in two orders and gets zero or one. An exact decimal reference shows the intended total for this particular input.
A compensated summation algorithm can reduce error for many datasets, but it is not an exact-money representation. BigDecimal is appropriate when decimal units are contractual; finite checks should precede any double aggregation.
Avoid accidental order contracts
Parallel reductions may combine partitions in different orders. If reports must be bitwise reproducible, define a stable order or use an exact accumulator with a bounded scale and range. Document the accepted error if approximate science data remains in double.
Working program
import java.math.BigDecimal;
public class SensorAggregateOrder {
static double sum(double[] readings) {
double total = 0;
for (double reading : readings) total += reading;
return total;
}
public static void main(String[] args) {
System.out.println(sum(new double[] {1e16, 1.0, -1e16}));
System.out.println(sum(new double[] {1e16, -1e16, 1.0}));
BigDecimal exact = new BigDecimal("10000000000000000")
.add(BigDecimal.ONE)
.subtract(new BigDecimal("10000000000000000"));
System.out.println(exact.toPlainString());
}
}Output
0.0
1.0
1Cost and ownership
Each double pass is O(n) time and O(1) extra storage. BigDecimal uses space and work that grow with precision. Neither ordering trick nor one sample output proves a general error bound for an arbitrary dataset.
Common Mistakes
- Do not assume addition is associative for double.
- Do not compare approximate aggregates as if their low bits were contractual.
- Do not convert binary doubles to decimal after precision has already been lost and expect recovery.
Read next
Java Double.isFinite: reject Infinity and NaN at input boundaries, Java Double NaN and signed zero: primitive and wrapper comparisons differ, Java BigDecimal: decimal amounts and explicit rounding, Java streams: lazy pipelines and bounded results.
