如何将float64数字截断为特定精度?

I want to truncate 1.234567 into a 3-fraction digit floating point number, but the result is not what I want.

E.g: 1.234567 => 1.234

package main

import (
    "strconv"
    "fmt"
)

func main() {
    f := 1.234567
    fmt.Println(strconv.FormatFloat(f, 'f', 3, 64)) //1.235
    fmt.Printf("%.3f", f) //1.235
}

Can anyone tell me how to do this in Go?

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

You need to truncate decimals manually, either on string level or with math.Floor like https://play.golang.org/p/UP2gFx2iFru.