go / checked

I use checked arithmetic to detect integer overflow before it causes silent bugs.

The problem

Go's integer arithmetic silently overflows:

var a int64 = math.MaxInt64
b := a + 1 // -9223372036854775808, no error

For financial calculations, counters, or any code where overflow would be a serious bug, you need explicit checks.

The pattern

package checked

import "math"

func AddInt64(a, b int64) (int64, bool) {
	if (b > 0 && a > math.MaxInt64-b) ||
		(b < 0 && a < math.MinInt64-b) {
		return 0, false
	}
	return a + b, true
}

func SubInt64(a, b int64) (int64, bool) {
	if (b > 0 && a < math.MinInt64+b) ||
		(b < 0 && a > math.MaxInt64+b) {
		return 0, false
	}
	return a - b, true
}

func MulInt64(a, b int64) (int64, bool) {
	if (a > 0 && b > 0 && a > math.MaxInt64/b) ||
		(a > 0 && b < 0 && b < math.MinInt64/a) ||
		(a < 0 && b > 0 && a < math.MinInt64/b) ||
		(a < 0 && b < 0 && b < math.MaxInt64/a) {
		return 0, false
	}
	return a * b, true
}

For unsigned integers, the checks are simpler:

func AddUint64(a, b uint64) (uint64, bool) {
	if math.MaxUint64-a < b {
		return 0, false
	}
	return a + b, true
}

func MulUint64(a, b uint64) (uint64, bool) {
	if b > 0 && a > math.MaxUint64/b {
		return 0, false
	}
	return a * b, true
}

Usage

total, ok := checked.AddInt64(balance, deposit)
if !ok {
	return errors.New("balance overflow")
}

product, ok := checked.MulUint64(price, quantity)
if !ok {
	return errors.New("total exceeds maximum")
}

When to use

When not to use

Alternative: panic variants

If overflow is always a bug and you want to fail fast, use a must pattern:

func MustAddInt64(a, b int64) int64 {
	sum, ok := AddInt64(a, b)
	if !ok {
		panic("integer overflow")
	}
	return sum
}

← All articles