【问题标题】:How to keep precision for big numbers in golang when converting from float to big.Int从float转换为big.Int时如何在golang中保持大数字的精度
【发布时间】:2022-10-23 16:52:13
【问题描述】:

我有一个输入可能是一个非常大或非常小的浮点数,需要将其转换为big.Int,但由于某种原因,存在一些精度损失。 我知道这应该发生在非常小的数字上,但是为什么会发生在大数字上,以及如何避免呢?

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

【问题讨论】:

  • 通过尝试将值存储在 float64 中,您甚至在使用 big.Float 之前已经失去了精度

标签: go


【解决方案1】:

全部正整数最多 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.01 * 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.0 * 2^53 = 9007199254740992

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


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

var x float64 = 827273999999999954

无法将此值表示为 float64。

package main

import (
    "fmt"
)

func main() {
    var x float64 = 827273999999999954

    fmt.Printf("%f
", 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
", 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)
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-27
    • 2011-03-30
    相关资源
    最近更新 更多