我的输入可能非常大或非常小,需要将其转换为
big.Int
,但由于某种原因,存在一些精度损失。
我知道这种情况应该发生在非常小的数字上,但是为什么会发生在大数字上,以及如何避免它?
直到 9007199254740992 的所有正整数都可以用 float64 表示,而不会损失任何精度。任何更高的值,您都会面临精度损失的风险,这种情况就发生在您的情况下。
给出原因的基本概念..
假设我们正在发明一种极其紧凑的方案,使用以下公式来表示浮点数:
m.mm * 10^+-e
..哪里:
通过这个,我们可以弄清楚可以表示什么范围的值:
所以这是一个相当不错的数字范围。
我们可以毫无困难地表示相当多的正整数,例如
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)
}