By banker's rounding I mean
- "round to nearest, ties to even"
Rounds to the nearest value; if the number falls midway it is rounded to the nearest value with an even (zero) least significant bit. This is the default for binary floating-point and the recommended default for decimal.
This method is said to be preferred over
- "round to nearest, ties away from zero"
on the grounds that it "minimizes the expected error when summing over rounded figures". Apparently this is because "it does not suffer from negative or positive bias as much as the round half away from zero method over most reasonable distributions".
I fail to see why that is the case. Intuitively, if 0.0
is rounded towards zero, 0.5
"should" be rounded away from zero (as in method 2). That way an equal amount of numbers would get rounded towards and away from zero. Put in simpler terms, if floating numbers were represented with 1 decimal figure, out of the ten numbers 0.0
, ..., 0.9
five would be rounded down and five would be rounded up with method 2. Similarly for 1.0
, ..., 1.9
etc.
Of course floating point numbers are represented with a binary mantissa, but I think the above reasoning still applies. Note that, for IEEE 754 double precision, both integers and integers-plus-half can be represented exactly for absolute values up to 2^52
approximately, and so those exact values actually show up in practice.
So how is method 1 better?
Yes! It really is more numerically stable.
For the case that you're looking at, the numbers
[0.0, 0.1, ..., 0.9]
, note that under round-ties-to-away, only four of those numbers are rounding down (0.1
through0.4
), five are rounded up, and one (0.0
) is unchanged by the rounding operation, and then of course that pattern repeats for1.0
through1.9
,2.0
through2.9
, etc. So on average, more values are rounded away from zero than towards it. But under round-ties-to-even, we'd get:[0.0, 0.9]
[1.0, 1.9]
and so on. On average, we get the same number of values rounding up as rounding down. More importantly, the expected error introduced by the rounding is (under suitable assumptions on the distribution of the inputs) closer to zero.
Here's a quick demonstration using Python. To avoid difficulties due to Python 2 / Python 3 differences in the builtin
round
function, we give two Python-version-agnostic rounding functions:Now we look at the average error introduced by applying these two functions on one-digit-after-the-point decimal values in the range
[50.0, 100.0]
:And we use the recently-added
statistics
standard library module to compute the mean and standard deviation of those errors:The key point here is that
errors_even
has zero mean: the average error is zero. Buterrors_away
has positive mean: the average error is biased away from zero.A more realistic example
Here's a semi-realistic example that demonstrates the bias from round-ties-away-from-zero in a numerical algorithm. We're going to compute the sum of a list of floating-point numbers, using the pairwise summation algorithm. This algorithm breaks the sum to be computed into two roughly equal parts, recursively sums those two parts, then adds the results. It's substantially more accurate than a naive sum, but typically not as good as more sophisticated algorithms like Kahan summation. It's the algorithm that's used by NumPy's
sum
function. Here's a simple Python implementation.We've included a parameter
add
to the function above, representing the operation to be used for addition. By default, it uses Python's normal addition algorithm, which on a typical machine will resolve to the standard IEEE 754 addition, using round-ties-to-even rounding mode.We want to look at the expected error from the
pairwise_sum
function, using both standard addition and using a round-ties-away-from-zero version of addition. Our first problem is that we don't have an easy and portable way to change the hardware's rounding mode from within Python, and a software implementation of binary floating-point would be large and slow. Fortunately, there's a trick we can use to get round-ties-away-from-zero while still using the hardware floating-point. For the first part of that trick, we can employ Knuth's "2Sum" algorithm to add two floats and obtain the correctly-rounded sum together with the exact error in that sum:With this in hand, we can easily use the error term to determine when the exact sum is a tie. We have a tie if and only if
error
is nonzero andsum + 2*error
is exactly representable, and in that casesum
andsum + 2*error
are the two floats nearest that tie. Using this idea, here's a function that adds two numbers and gives a correctly rounded result, but rounds ties away from zero.Now we can compare results.
sample_sum_errors
is a function that generates a list of floats in the range [1, 2], adds them using both normal round-ties-to-even addition and our custom round-ties-away-from-zero version, compares with the exact sum and returns the errors for both versions, measured in units in the last place.Here's an example run:
So that's an error of 1.6 ulps using the standard addition, and an error of 9.6 ulps when rounding ties away from zero. It certainly looks as though the ties-away-from-zero method is worse, but a single run isn't particularly convincing. Let's do this 10000 times, with a different random sample each time, and plot the errors we get. Here's the code:
When I run the above function on my machine, I see:
and I get the following plot:
I was planning to go one step further and perform a statistical test for bias on the two samples, but the bias from the ties-away-from-zero method is so marked that that looks unnecessary. Interestingly, while the ties-away-from-zero method gives poorer result, it does give a smaller spread of errors.