Using a temporary variable to operate on math/big objects in Go
On one of my side projects, I opted to use the Go standard library’s math/big
classes, in particular big.Rat
, which is one of the best class names I’ve encountered in a long time. bit.Rat
models rational numbers (of the type a/b) of arbitrary precision, which I’m using for a finance app I’m working on.
(There are two other classes as well, big.Nat
(integers) and big.Float
, which also have the limitation described below.)
big.Rat
has a funny API:
var a, b, c big.Rat
// Some calculations...
a.Add(&b, &c) // a = b + c
Binary operations like .Add
typically take two operands and assign the result to the receiver (a
in the example above).
However, there is an explicit caveat in the documentation for most such operations: you’re not supposed to pass the receiver in as an argument. In other words: no aliasing. More precisely, the documentation states (emphasis mine):
Operations always take pointer arguments (*Rat) rather than Rat values, and each unique Rat value requires its own unique *Rat pointer. To “copy” a Rat value, an existing (or newly allocated) Rat must be set to a new value using the Rat.Set method; shallow copies of Rats are not supported and may lead to errors.
So the following, although it may seem to work fine, is technically forbidden:
func average(l []big.Rat) big.Rat {
// DON'T DO THIS!!!
var avg big.Rat
for _, v := range l {
avg.Add(&avg, &v) // Aliasing: avg = avg + v
}
avg.Quot(&avg, len(l)) // Aliasing: avg = avg / len(l)
return avg
}
I’m not sure how many users math/big
has, but there is surprisingly little to be found about this specific use case online, so I thought I’d document it here.
It seems the easiest way to operate safely on big.Rat
is to use a local temporary variable:
func average(l []big.Rat) big.Rat {
var avg big.Rat
var tmp big.Rat // Temp variable for binary operations
for _, v := range l {
avg.Set(tmp.Add(&avg, &v)) // Correct: avg = avg + v
}
avg.Set(tmp.Quot(&avg, len(l))) // Correct: avg = avg / len(l)
return avg
}
You can keep the tmp
variable on the stack and re-use it for every computation to minimize the overhead.