Definition
- Average (arithmetic mean) – the sum of all elements divided by the number of elements, giving a single representative value.
- Running sum – a
BigDecimalaccumulator initialized toBigDecimal.ZEROand updated withsum = sum.add(current)for each element. - Safe division –
sum.divide(new BigDecimal(count), 2, RoundingMode.HALF_UP)specifies two decimal places and a rounding mode to avoidArithmeticException. - Time complexity – O(n) for the single summation pass; O(1) extra space for the accumulator.
- Empty guard – averaging zero elements is mathematically undefined, so the method must reject an empty array before dividing.
Rajesh wants to know his average monthly spending at National Bank of India. He adds up all twelve monthly spend figures into one running total, then divides that total by twelve. The result is his typical monthly spend, which Priya on the analytics team uses for budgeting recommendations.
Understanding the Concepts
What is the average of an array?
The average, also called the arithmetic mean, is the sum of all elements divided by the number of elements. It gives a single value that represents the central tendency of the data. For an array of monthly spending amounts like 5000, 7000, and 6000 rupees, the average is 18000 divided by 3, which is 6000 rupees.
Why use BigDecimal for money averages?
Binary floating-point types like double cannot represent common decimal fractions exactly. The value 0.10 in double is actually 0.1000000000000000055511151231257827021181583404541015625. When you add many such values the error accumulates, and a bank statement ends up a paisa wrong. BigDecimal stores the value as an unscaled integer and a scale, so 6000.00 is exactly 600000 with scale 2, no approximation.
package com.bank.service;
import java.math.BigDecimal;
import java.math.RoundingMode;
public class AverageCalculator {
public static BigDecimal average(BigDecimal[] amounts) {
if (amounts == null || amounts.length == 0) {
throw new IllegalArgumentException("Cannot average an empty array");
}
BigDecimal sum = BigDecimal.ZERO;
for (BigDecimal amount : amounts) {
sum = sum.add(amount);
}
return sum.divide(new BigDecimal(amounts.length), 2, RoundingMode.HALF_UP);
}
public static void main(String[] args) {
BigDecimal[] monthlySpend = {
new BigDecimal("5000.00"),
new BigDecimal("7000.00"),
new BigDecimal("6000.00")
};
System.out.println("Average monthly spend: " + average(monthlySpend));
}
}
Average monthly spend: 6000.00
How does the division work internally?
BigDecimal division can produce a non-terminating decimal. For example, 10000 divided by 3 is 3333.333... which never ends. If you call sum.divide(count) without a scale and rounding mode, Java throws an ArithmeticException with the message "Non-terminating decimal expansion." That is actually a safety feature, because it forces you to decide how to round rather than silently losing digits.
The safe call is:
sum.divide(new BigDecimal(count), 2, RoundingMode.HALF_UP);
This says: give me a result with 2 decimal places, and if rounding is needed, round half up. For customer-facing displays, HALF_UP is the standard because it matches what people expect. For internal aggregations across thousands of accounts, HALF_EVEN (banker's rounding) reduces cumulative bias.
What happens with an empty array?
Averaging zero elements is undefined because you would divide by zero. The method must detect this and reject it before attempting the division.
// Wrong: no guard
public static BigDecimal averageUnsafe(BigDecimal[] amounts) {
BigDecimal sum = BigDecimal.ZERO;
for (BigDecimal a : amounts) sum = sum.add(a);
return sum.divide(new BigDecimal(amounts.length), 2, RoundingMode.HALF_UP);
// throws ArithmeticException: Division by zero when amounts.length == 0
}
// Correct: guard the empty case
public static BigDecimal averageSafe(BigDecimal[] amounts) {
if (amounts == null || amounts.length == 0) {
throw new IllegalArgumentException("Cannot average an empty array");
}
BigDecimal sum = BigDecimal.ZERO;
for (BigDecimal a : amounts) sum = sum.add(a);
return sum.divide(new BigDecimal(amounts.length), 2, RoundingMode.HALF_UP);
}
How is this different from a weighted average?
A plain average treats every element equally. A weighted average multiplies each element by a weight before summing.
| Feature | Plain average | Weighted average |
|---|---|---|
| Formula | sum / count | weighted sum / total weight |
| Weights | all equal to 1 | different per element |
| Use case | monthly spend average | time-weighted average balance |
| Banking example | average of 12 monthly totals | balance held for different day counts |
// Weighted average: balance * days held
BigDecimal weightedSum = BigDecimal.ZERO;
int totalDays = 0;
for (int i = 0; i < balances.length; i++) {
weightedSum = weightedSum.add(balances[i].multiply(new BigDecimal(days[i])));
totalDays += days[i];
}
BigDecimal weightedAvg = weightedSum.divide(new BigDecimal(totalDays), 2, RoundingMode.HALF_UP);
What are common mistakes?
The most common mistake is using double for money.
// Wrong: double accumulates binary rounding error
double sum = 0.0;
for (double d : new double[]{0.1, 0.2, 0.3}) sum += d;
System.out.println(sum / 3); // 0.20000000000000004, not 0.20
// Correct: BigDecimal is exact
BigDecimal bdSum = BigDecimal.ZERO;
for (String s : new String[]{"0.10", "0.20", "0.30"}) {
bdSum = bdSum.add(new BigDecimal(s));
}
System.out.println(bdSum.divide(new BigDecimal(3), 2, RoundingMode.HALF_UP)); // 0.20
Another mistake is constructing BigDecimal from a double literal instead of a String.
// Wrong: captures the binary error
new BigDecimal(0.1); // 0.1000000000000000055511151...
// Correct: exact
new BigDecimal("0.1"); // 0.1
What about using Java Streams?
Java streams do not have a built-in BigDecimal average collector, so you reduce manually.
import java.util.Arrays;
BigDecimal[] amounts = { new BigDecimal("5000"), new BigDecimal("7000"), new BigDecimal("6000") };
BigDecimal streamSum = Arrays.stream(amounts).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal avg = streamSum.divide(new BigDecimal(amounts.length), 2, RoundingMode.HALF_UP);
For primitive int or double arrays, IntStream.average() or DoubleStream.average() exist, but they return OptionalDouble and use floating point, which is not acceptable for money.
What should a senior engineer know?
A senior engineer knows that the choice of rounding mode is a business decision, not a coding convenience. HALF_UP rounds 2.5 to 3, which customers expect. HALF_EVEN rounds 2.5 to 2 and 3.5 to 4, which avoids systematic upward bias across millions of accounts. The rounding mode should be defined in a shared constant or configuration, not scattered across the codebase.
For very large arrays where the sum could exceed BigDecimal's practical range, a senior engineer would use a partitioned parallel stream with reduce and merge the partial sums, keeping the accumulation exact throughout.
What are common interview follow-up questions?
- How would you compute a running average that updates as new elements arrive without re-scanning the array?
- How would you handle null elements in the array?
- What is the difference between HALF_UP and HALF_EVEN rounding, and when do you use each?
Visual Overview
┌─────────────────────────┐
│ Input: BigDecimal[] │
└───────────┬─────────────┘
│
▼
┌─────────────────────────┐
│ length == 0? │
│ Yes → throw exception │
│ No → continue │
└───────────┬─────────────┘
│
▼
┌─────────────────────────┐
│ sum = BigDecimal.ZERO │
└───────────┬─────────────┘
│
▼
┌─────────────────────────┐
│ for each element: │
│ sum = sum.add(elem) │
└───────────┬─────────────┘
│
▼
┌─────────────────────────────────────┐
│ avg = sum.divide(count, 2, HALF_UP)│
└───────────┬─────────────────────────┘
│
▼
┌─────────────────────────┐
│ return avg │
└─────────────────────────┘
Key Takeaway
Finding the average of an array is a two-step linear operation. First you sum every element in a single pass, and then you divide the sum by the number of elements. The summation is linear time and you keep only one accumulator, so the extra space is constant.
In a banking project at National Bank of India, Rajesh needed a budgeting insight showing his average monthly spend. The monthly spend figures were accumulated into a running BigDecimal total and divided by the number of months. Every amount was a BigDecimal built from a String, never from a double literal, so the sum was exact to the paisa.
The part that matters most for a financial system is the division. BigDecimal division without a rounding instruction throws an ArithmeticException whenever the result does not terminate, such as a total that does not divide evenly by the count. The safe call always specifies a scale and a rounding mode: two decimal places with HALF_UP for customer-facing display, or HALF_EVEN for internal aggregations across many accounts to avoid cumulative bias.
The empty-array guard is non-negotiable because averaging zero elements is undefined, and dividing by zero would crash. Throwing an IllegalArgumentException with a clear message is better than letting an ArithmeticException leak from the division.
Remember one sentence: average is sum divided by count, and for money that division must always specify a scale and a rounding mode.
---