Unwanted Bias from Standard Rounding

When aggregating large volumes of financial or statistical numbers, standard arithmetic rounding (math.Round) introduces a subtle upward drift in total sums. Because math.Round always rounds .5 away from zero (2.5 -> 3, 3.5 -> 4), any dataset with frequent half-way values accumulates a positive bias over thousands of operations.

Half-to-Even (Banker’s) Rounding

To eliminate this directional bias, financial systems typically use Half-to-Even rounding (also called Banker’s Rounding).

Instead of always rounding .5 up, Half-to-Even rounds an exact .5 fraction to the nearest even integer:

  • 2.5 rounds down to 2 (since 2 is even)
  • 3.5 rounds up to 4 (since 4 is even)

Over a large dataset, half-way values round up and down roughly equally, canceling out cumulative drift.

Implementing It in Go

Since Go 1.10, the standard library’s math package includes math.RoundToEven out of the box, so you don’t need to roll your own modulo/floor logic (which is also easy to get wrong for negative numbers):

package main

import (
	"fmt"
	"math"
)

// roundCentsToEven rounds a monetary amount to 2 decimal places using Banker's Rounding.
func roundCentsToEven(amount float64) float64 {
	return math.RoundToEven(amount*100) / 100
}

func main() {
	fmt.Printf("2.5  -> %v\n", math.RoundToEven(2.5))  // 2 (nearest even)
	fmt.Printf("3.5  -> %v\n", math.RoundToEven(3.5))  // 4 (nearest even)
	fmt.Printf("-2.5 -> %v\n", math.RoundToEven(-2.5)) // -2 (nearest even)
	fmt.Printf("2.6  -> %v\n", math.RoundToEven(2.6))  // 3 (standard rounding)
	fmt.Printf("2.4  -> %v\n", math.RoundToEven(2.4))  // 2 (standard rounding)

	fmt.Printf("10.125 cents -> %.2f\n", roundCentsToEven(10.125)) // 10.12
	fmt.Printf("10.135 cents -> %.2f\n", roundCentsToEven(10.135)) // 10.14
}

Note on floating-point precision: If you are dealing with ledger balances or currency conversion in production, float64 binary representation can still turn a decimal like 1.005 into 1.0049999999999998934... before math.RoundToEven sees it. For strict decimal arithmetic in Go, prefer integer minor units (cents) or a decimal package like shopspring/decimal (which uses Banker’s Rounding via RoundBank).