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

iko*_*ool 7 floating-point truncate go

我想截断1.234567为3位小数浮点数,但结果不是我想要的。

例如: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
}
Run Code Online (Sandbox Code Playgroud)

谁能告诉我如何在 Go 中做到这一点?

icz*_*cza 11

天真的方式(并不总是正确的)

\n\n

对于截断,我们可以利用math.Trunc()它丢弃小数位。这并不完全是我们想要的,我们想保留一些小数位。因此,为了达到我们想要的目的,我们可以先将输入乘以10的幂,将想要的小数位移至“整数”部分,然后截断后(调用math.Trunc()它会丢弃剩余的小数位),我们可以除以我们一开始所乘的 10 次方:

\n\n
f2 := math.Trunc(f*1000) / 1000\n
Run Code Online (Sandbox Code Playgroud)\n\n

将其包装到一个函数中:

\n\n
func truncateNaive(f float64, unit float64) float64 {\n    return math.Trunc(f/unit) * unit\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

测试它:

\n\n
f := 1.234567\nf2 := truncateNaive(f, 0.001)\nfmt.Printf("%.10f\\n", f2)\n
Run Code Online (Sandbox Code Playgroud)\n\n

输出:

\n\n
1.2340000000\n
Run Code Online (Sandbox Code Playgroud)\n\n

到目前为止一切顺利,但请注意,我们在内部执行算术运算truncateNaive(),这可能会导致不需要的舍入,这可能会改变函数的输出。

\n\n

例如,如果输入是0.299999999999999988897769753748434595763683319091796875(它可以float64准确地由一个值表示,请参见证明),则输出应该是0.2999000000,但它会是其他东西:

\n\n
f = 0.299999999999999988897769753748434595763683319091796875\nf2 = truncateNaive(f, 0.001)\nfmt.Printf("%.10f\\n", f2)\n
Run Code Online (Sandbox Code Playgroud)\n\n

输出:

\n\n
0.3000000000\n
Run Code Online (Sandbox Code Playgroud)\n\n

在Go Playground上尝试这些。

\n\n

在大多数情况下,这个错误的输出可能是不可接受的(除非您从输入非常接近0.3\xe2\x80\x93 的角度来看它,差异小于 10 -16 \xe2\x80\x93 ,而输出为0.3。 ..)。

\n\n

使用big.Float

\n\n

为了正确截断所有有效值float64,中间运算必须精确。要实现这一点,仅使用单个float64是不够的。有多种方法可以将输入分成 2 个float64值并对它们执行运算(因此精度不会丢失),这会更有效,或者我们可以使用更方便的方法,big.Float它可以是任意精度。

\n\n

这是上述truncateNaive()函数的“脚本”,使用big.Float

\n\n
func truncate(f float64, unit float64) float64 {\n    bf := big.NewFloat(0).SetPrec(1000).SetFloat64(f)\n    bu := big.NewFloat(0).SetPrec(1000).SetFloat64(unit)\n\n    bf.Quo(bf, bu)\n\n    // Truncate:\n    i := big.NewInt(0)\n    bf.Int(i)\n    bf.SetInt(i)\n\n    f, _ = bf.Mul(bf, bu).Float64()\n    return f\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

测试它:

\n\n
f := 1.234567\nf2 := truncate(f, 0.001)\nfmt.Printf("%.10f\\n", f2)\n\nf = 0.299999999999999988897769753748434595763683319091796875\nf2 = truncate(f, 0.001)\nfmt.Printf("%.10f\\n", f2)\n
Run Code Online (Sandbox Code Playgroud)\n\n

输出现在有效(在Go Playground上尝试):

\n\n
1.2340000000\n0.2990000000\n
Run Code Online (Sandbox Code Playgroud)\n