如何以正确的方式将 float64 数字更改为 uint64?

How to change a float64 number to uint64 in a right way?

package main

func main() {
    var n float64 = 6161047830682206209
    println(uint64(n))
}

输出将是:

6161047830682206208

貌似当float64改成uint64时,小数部分被舍弃了。

这里的问题是常量和浮点数的表示。

常量以任意精度表示。浮点数使用 IEEE 754 标准表示。

Spec: Constants:

Numeric constants represent values of arbitrary precision and do not overflow.

Spec: Numeric types:

float64     the set of all IEEE-754 64-bit floating-point numbers

在 IEEE 754 中,使用 64 位的双精度(float64 在 Go 中)53 位 用于存储数字。这意味着可以表示的最大位数(max number)是 2<<52 的位数(1 位用于符号):

2<<52        : 9007199254740992
Your constant: 6161047830682206209

精确到 15.95 位(16 位,但不是所有可以用 16 位描述的值,最多只能 9007199254740992)。

您尝试放入 float64 类型变量的整数常量 不适合 52 位,因此必须对其进行四舍五入和数字(或位) 将被切断(丢失)。

您可以通过打印原始 n float64 号码来验证这一点:

var n float64 = 6161047830682206209
fmt.Printf("%f\n", n)
fmt.Printf("%d\n", uint64(n))

输出:

6161047830682206208.000000
6161047830682206208

问题不在于转换,问题在于您尝试转换的 float64 值已经不等于您尝试分配给它的常量。

纯属好奇:

尝试使用更大的数字进行相同操作:与第一个常量相比 +500:

n = 6161047830682206709 // +500 compared to first!
fmt.Printf("%f\n", n2)
fmt.Printf("%d\n", uint64(n2))

输出仍然一样(最后的数字/位被截掉,包括+500!):

6161047830682206208.000000
6161047830682206208

尝试使用 52 位(小于 ~16 位)精确表示数字的较小数字:

n = 7830682206209
fmt.Printf("%f\n", n)
fmt.Printf("%d\n", uint64(n))

输出:

7830682206209.000000
7830682206209

Go Playground 上试试。

问题不在于转换,而在于数字太大,无法完全存储为float64。对 n 的赋值会导致失去一些意义。

如果你仔细想想,你的数字(大约 6e18)非常接近最大的 uint64(2^64-1 或大约 18e18)。 uint64 使用它所有的 64 位来存储一个整数,但是 float64 必须使用它的 64 位中的一些来存储指数,所以它会用更少的位来记住尾数。

总而言之,如果您将大于 10^15 的 uint64(或整数常量)分配给 float64 并返回给 uint64,您将得到一个关闭,但可能不完全一样,值。

[BTW Icza 的回答很好而且正确。我希望这个答案是简单的总结。]