从 float 转换为 big.Int 时如何保持 golang 中大数的精度

问题描述 投票:0回答:1

我的输入可能非常大或非常小,需要将其转换为

big.Int
,但由于某种原因,存在一些精度损失。 我知道这种情况应该发生在非常小的数字上,但是为什么会发生在大数字上,以及如何避免它?

https://go.dev/play/p/AySnKAikSRx

go floating-point precision
1个回答
2
投票

直到 9007199254740992 的所有正整数都可以用 float64 表示,而不会损失任何精度。任何更高的值,您都会面临精度损失的风险,这种情况就发生在您的情况下。


给出原因的基本概念..

假设我们正在发明一种极其紧凑的方案,使用以下公式来表示浮点数:

m.mm * 10^+-e

..哪里:

  • e = 指数,[1-9]
  • m.mm = 尾数 [0.01-9.99]

通过这个,我们可以弄清楚可以表示什么范围的值:

  • 最低 = 0.01 * 10^-9 = 0.00000000001
  • 最高 = 9.99 * 10^9 = 9990000000

所以这是一个相当不错的数字范围。

我们可以毫无困难地表示相当多的正整数,例如

1   = 1.00 * 10^0
2   = 2.00 * 10^0
3   = 3.00 * 10^0
⋮
10  = 1.00 * 10^1
11  = 1.10 * 10^1
12  = 1.20 * 10^1
⋮
100 = 1.00 * 10^2
101 = 1.01 * 10^2
102 = 1.02 * 10^2
⋮
999 = 9.99 * 10^2

当我们超过

9.99 * 10^2
时,问题就开始了。代表1000不是问题:

1000 = 1.00 * 10^3

但是如何表示1001呢?下一个可能的值是

1.01 * 10^3 = 1010

这意味着 +9 精度损失,因此我们必须选择

1.00 * 10^3
,精度损失为 -1。

上面本质上是 float64 的表现方式,除了基数 2 和 52 位尾数。全部 52 位设置完毕,然后加 1,值为:

1.0 * 2^53 = 9007199254740992

因此可以在不损失精度的情况下表示所有达到该值的正整数。高于此值的整数可能会导致精度损失 - 这在很大程度上取决于该值。


现在,Go 代码中引用的值:

var x float64 = 827273999999999954

无法将此精确值表示为 float64。

package main

import (
    "fmt"
)

func main() {
    var x float64 = 827273999999999954

    fmt.Printf("%f\n", x)
}

产量..

827274000000000000.000000

因此,在初始化

x
时,精度基本上会丢失。但什么时候会发生呢?如果我们跑..

$ go build -o tmp
$ go tool objdump tmp

并搜索

TEXT main.main(SB)
,我们可以找到指令:

main.go:10            0x108b654               48b840d5cba322f6a643    MOVQ $0x43a6f622a3cbd540, AX

因此

0x43a6f622a3cbd540
被设置为 AX - 这是我们的 float64 值。

package main

import (
    "fmt"
    "math"
)

func main() {
    fmt.Printf("float: %f\n", math.Float64frombits(0x43a6f622a3cbd540))
}

打印

float: 827274000000000000.000000

所以精度在编译时基本上已经丢失了(这是有道理的)。因此,在带有

big.NewFloat(x).Int(nil)
的代码行中,作为
x
传递的值是
827274000000000000.000000


如何避免?

用你提供的代码,没有办法。

如果您能够将值表示为整数..

package main

import (
    "fmt"
    "math/big"
)

func main() {
    var x uint64 = 827273999999999954

    bf := (&big.Float{}).SetUint64(x)
    
    fmt.Println(bf)
}

产量

8.27273999999999954e+17

这是您期望的值。或者通过字符串:

package main

import (
    "fmt"
    "math/big"
)

func main() {
    var x string = "827273999999999954"

    bf, ok := (&big.Float{}).SetString(x)
    if !ok {
        panic("failed to set string")
    }

    fmt.Println(bf)
}
© www.soinside.com 2019 - 2024. All rights reserved.