如何以正确的方式将 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 标准表示。
Numeric constants represent values of arbitrary precision and do not overflow.
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 的回答很好而且正确。我希望这个答案是简单的总结。]
package main
func main() {
var n float64 = 6161047830682206209
println(uint64(n))
}
输出将是:
6161047830682206208
貌似当float64
改成uint64
时,小数部分被舍弃了。
这里的问题是常量和浮点数的表示。
常量以任意精度表示。浮点数使用 IEEE 754 标准表示。
Numeric constants represent values of arbitrary precision and do not overflow.
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 的回答很好而且正确。我希望这个答案是简单的总结。]