【问题标题】:How can I truncate float64 number to a particular precision?如何将 float64 数字截断为特定精度?
【发布时间】:2019-01-25 09:52:27
【问题描述】:

我想将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
}

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

【问题讨论】:

  • 您想要的是将(或截断)四舍五入(或截断)为 3 个小数位,而不是位。对于一般舍入,请参阅可能的重复项:Golang Round to Nearest 0.05
  • 我不想要四舍五入,只是截断到 3 个小数位。 1.234567 => 1.234 不是 1.235

标签: go floating-point truncate


【解决方案1】:

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

对于截断,我们可以利用math.Trunc() 来丢弃小数位。这不完全是我们想要的,我们想要保留一些小数位数。所以为了达到我们想要的效果,我们可以先将输入乘以 10 的幂,将想要的小数位数移动到“整数”部分,然后在截断之后(调用 math.Trunc() 将丢弃剩余的小数位数) ,我们可以除以开始时乘以的 10 的幂:

f2 := math.Trunc(f*1000) / 1000

将其包装成一个函数:

func truncateNaive(f float64, unit float64) float64 {
    return math.Trunc(f/unit) * unit
}

测试它:

f := 1.234567
f2 := truncateNaive(f, 0.001)
fmt.Printf("%.10f\n", f2)

输出:

1.2340000000

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

例如,如果输入是0.299999999999999988897769753748434595763683319091796875(它可以精确地用float64 值表示,请参阅proof),则输出应该是0.2999000000,但它会是别的东西:

f = 0.299999999999999988897769753748434595763683319091796875
f2 = truncateNaive(f, 0.001)
fmt.Printf("%.10f\n", f2)

输出:

0.3000000000

Go Playground 上试试这些。

这个错误的输出在大多数情况下可能是不可接受的(除非你从输入非常接近0.3的方式来看它——差异小于10- 16——输出为0.3...)。

使用big.Float

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

这是上述truncateNaive()函数使用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
}

测试它:

f := 1.234567
f2 := truncate(f, 0.001)
fmt.Printf("%.10f\n", f2)

f = 0.299999999999999988897769753748434595763683319091796875
f2 = truncate(f, 0.001)
fmt.Printf("%.10f\n", f2)

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

1.2340000000
0.2990000000

【讨论】:

  • f = 1.234 f2 := truncate(f, 0.001) fmt.Printf("%.10f\n", f2) //1.2330000000
  • @ikool 那是因为1.234 不能完全用float64 表示。当您将1.234 分配给float64 时,这将是1.233999999999999985789145284798,请参阅Go Playground。所以基本上你截断了1.233999999999999985789145284798,正确的输出是1.233
【解决方案2】:

您需要手动截断小数,无论是在字符串级别还是使用math.Floor(如https://play.golang.org/p/UP2gFx2iFru)。

【讨论】:

  • 如果输入是0.299999999999999988897769753748434595763683319091796875,这会给出错误的结果,请参阅我的答案。
  • 这个数字不适合 float64。 ;-) 但你是对的,当你击中很多 9 时,出现圆角错误的可能性很小。
  • 这个数字完全可以用float64 来表示,参见play.golang.org/p/I-H2RQJ6Lj4
  • float64 限制为 15 位小数。由于下面的算法,您有趣的示例呈现为这个精确的小数序列​​。尝试更改数字@icza,如:play.golang.org/p/pXHwWCyTH-o。 ;-)
  • float64 类型是二进制表示 (IEEE 754),它有有限位来表示数字,但有限二进制数可能有更多十进制数字。就像有限的0.3 十进制数具有无限的二进制表示。 (链接的维基百科页面有这样的例子,检查一下。)
猜你喜欢
  • 2011-02-21
  • 1970-01-01
  • 2014-08-16
  • 1970-01-01
  • 2019-03-06
  • 1970-01-01
  • 1970-01-01
  • 2017-11-29
相关资源
最近更新 更多