statistics: fmean() Reintroduces Float Error on Decimal (tested)

statistics.fmean() is documented as a faster alternative to mean() that always returns a float. We measured what that means in practice when the input is Decimal data -- the exact type chosen specifically to avoid binary floating-point rounding error.

1. mean() stays exact on Decimal input; fmean() doesn't

from decimal import Decimal
import statistics as st

data = [Decimal("1.1"), Decimal("2.2"), Decimal("3.3")]

print(st.mean(data), type(st.mean(data)))
print(st.fmean(data), type(st.fmean(data)))
2.2 
2.1999999999999997 

Measured: mean() on three Decimal values returns the exact Decimal('2.2'), preserving the input type and exactness. fmean() on the identical data returns 2.1999999999999997 -- a float with the classic binary floating-point rounding error, one ULP off the true value.

2. The error shows up even on data that looks "safe"

from decimal import Decimal
import statistics as st

data = [Decimal("0.1")] * 10

print(st.mean(data))
print(st.fmean(data))
print(sum(data))
0.1
0.1
1.0

Measured: this particular case happens to round-trip cleanly for both functions -- but it's not something to rely on. fmean() converts every element to float internally before summing and dividing, so whether a given Decimal dataset comes out exact or not depends on whether the specific values happen to have an exact binary floating-point representation, not on anything the caller controls.

3. This is documented behavior, not a bug -- fmean() always returns float

import statistics as st

print(st.fmean([1, 2, 3]))        # ints in
print(st.fmean([1, 2, 3]) + 0)
print(type(st.fmean([1, 2, 3])))
2.0
2.0

Measured: fmean() returns float even for plain int input where the exact answer is representable -- the "f" in the name stands for float, and the function is documented to always coerce through float for speed, regardless of input type. The gotcha isn't that this happens; it's that fmean()'s signature looks identical to mean()'s, so a speed-motivated find-and-replace from mean() to fmean() on Decimal-based financial or scientific data silently trades exactness for speed with no warning or type error at the call site.

Frequently asked questions

Does statistics.fmean() preserve Decimal precision like mean() does?

No -- we measured fmean([Decimal('1.1'), Decimal('2.2'), Decimal('3.3')]) returning the float 2.1999999999999997, while mean() on the same data returns the exact Decimal('2.2').

Why does statistics.fmean() lose precision on Decimal input?

fmean() is documented to convert its input through float internally for speed, always returning a float result regardless of input type. Decimal values get converted to their closest binary float approximation before summing, which can reintroduce the exact rounding error Decimal is used to avoid.

Does statistics.fmean() always produce a less precise result than mean()?

Not always -- we measured a case (ten copies of Decimal('0.1')) where both functions returned the same value, 0.1, because that specific value happens to survive the float round-trip. Whether a given Decimal dataset survives depends on the specific values, not on anything the caller controls.

Is statistics.fmean() returning float for Decimal input a bug?

No -- it's documented: fmean() always returns float, even for plain int input. The risk is that swapping mean() for fmean() purely for speed, without checking the input type, silently discards Decimal's exactness guarantee with no error raised.

Written with AI assistance. Measured on 2026-10-04 with Python 3.13.5's built-in statistics module: computed mean() and fmean() on the same list of Decimal values and compared the results and their types. PlainHeaders is our extension; Python's statistics module is built in.