The naive way (not always correct)
For truncation, we could take advantage of math.Trunc()
which throws away the fraction digits. This is not exactly what we want, we want to keep some fraction digits. So in order to achieve what we want, we may first multiply the input by a power of 10 to shift the wanted fraction digits to the "integer" part, and after truncation (calling math.Trunc()
which will throw away the remaining fraction digits), we can divide by the same power of 10 we multiplied in the beginning:
f2 := math.Trunc(f*1000) / 1000
Wrapping this into a function:
func truncateNaive(f float64, unit float64) float64 {
return math.Trunc(f/unit) * unit
}
Testing it:
f := 1.234567
f2 := truncateNaive(f, 0.001)
fmt.Printf("%.10f
", f2)
Output:
1.2340000000
So far so good, but note that we perform arithmetic operations inside truncateNaive()
which may result in unwanted roundings, which could alter the output of the function.
For example, if the input is 0.299999999999999988897769753748434595763683319091796875
(it's representable by a float64
value exactly, see proof), the output should be 0.2999000000
, but it will be something else:
f = 0.299999999999999988897769753748434595763683319091796875
f2 = truncateNaive(f, 0.001)
fmt.Printf("%.10f
", f2)
Output:
0.3000000000
Try these on the Go Playground.
This wrong output is probably not acceptable in most cases (except if you look at it from a way that the input is very close to 0.3
–difference is less than 10-16–to which the output is 0.3
...).
Using big.Float
To properly truncate all valid float64
values, the intermediate operations must be precise. To achieve that, using a single float64
is insufficient. There are ways to split the input into 2 float64
values and perform operations on them (so precision is not lost) which would be more efficient, or we could use a more convenient way, big.Float
which can be of arbitrary precision.
Here's the "transcript" of the above truncateNaive()
function using big.Float
:
func truncate(f float64, unit float64) float64 {
bf := big.NewFloat(0).SetPrec(1000).SetFloat64(f)
bu := big.NewFloat(0).SetPrec(1000).SetFloat64(unit)
bf.Quo(bf, bu)
// Truncate:
i := big.NewInt(0)
bf.Int(i)
bf.SetInt(i)
f, _ = bf.Mul(bf, bu).Float64()
return f
}
Testing it:
f := 1.234567
f2 := truncate(f, 0.001)
fmt.Printf("%.10f
", f2)
f = 0.299999999999999988897769753748434595763683319091796875
f2 = truncate(f, 0.001)
fmt.Printf("%.10f
", f2)
Output is now valid (try it on the Go Playground):
1.2340000000
0.2990000000