为什么在 golang 中左移 64 位不会溢出?
Why doesn't left bit shifting by 64 overflow in golang?
我正在查看 A Tour of Go,但我对他们的基本 types.go 示例中的某些内容感到困惑:
MaxInt uint64 = 1<<64 - 1
在无符号 64 位整数中向左移动 1 64 个位置不应该导致溢出(a.k.a。移动超过 MSB 位)?
但是,直到该行更改为:
,编译器才会报错
MaxInt uint64 = 1<<65 - 1
./basic-types.go:5: constant 36893488147419103231 overflows uint64
如果我编写一些代码来迭代不同长度的左移,包括如上例所示的导致编译器呕吐的 65 位移位,我会看到两件事:
它的行为符合我的预期,因为 1<<63
将 1 放在 uint64
可能的 MSB 中
不会溢出了(咦?!?!)
代码:
package main
import "fmt"
func main() {
for i := 60; i < 66; i++ {
var j uint64 = 1 << uint64(i) - 1
fmt.Printf("%2d | %64b | %#18x\n", i, j, j)
}
输出:
60 | 111111111111111111111111111111111111111111111111111111111111 | 0xfffffffffffffff
61 | 1111111111111111111111111111111111111111111111111111111111111 | 0x1fffffffffffffff
62 | 11111111111111111111111111111111111111111111111111111111111111 | 0x3fffffffffffffff
63 | 111111111111111111111111111111111111111111111111111111111111111 | 0x7fffffffffffffff
64 | 1111111111111111111111111111111111111111111111111111111111111111 | 0xffffffffffffffff
65 | 1111111111111111111111111111111111111111111111111111111111111111 | 0xffffffffffffffff
写的时候
1<<64
上面的 1
不是 int64
。它是一个常量文字。来自语言规范:
Constant expressions are always evaluated exactly; intermediate values
and the constants themselves may require precision significantly
larger than supported by any predeclared type in the language.
因此,常量文字是在编译时计算的,并且可能非常大,因为它不是语言实现的特定类型。
下面实际上会给出溢出错误:
var i int64
i = 1<<65 - 1
因为现在常量文字表达式的计算结果大于 int64
可以包含的值。
阅读更多相关信息 here。
要了解为什么您的示例代码适用于 i = 65
,请参阅 Golang specs 中的以下规范:
The right operand in a shift expression must have unsigned integer
type or be an untyped constant that can be converted to unsigned
integer type. If the left operand of a non-constant shift expression
is an untyped constant, it is first converted to the type it would
assume if the shift expression were replaced by its left operand
alone.
上面的粗体部分与您的代码有关。考虑以下代码:
a := 66
var j uint64 = 1<<uint64(a) - 1
此处在移位运算符中,右操作数是一个非常量表达式。所以整个移位运算就变成了非常量移位表达式。因此,如上所述,左操作数 1
被转换为 uint64
.
现在,正在 uint64(1)
上进行转换,可以使用 <<
将其转换到任意多个位置。您可以将它移动到 64 位以外,并且实现很容易允许它。但在这种情况下,上面保存 uint64(1)
的内存将包含所有零。
请注意,根据语言规范,此行为与溢出不同。同样,只要正确的运算符不是常量表达式,语言实现就允许进行尽可能多的转换。因此,例如,这将起作用:
a := 6666
var j uint64 = 1<<uint64(a) - 1 // non-constant shift expression
这样想。早些时候,1
是无类型的。它具有任意精度(取决于实现)并且返回整数(所有位)。现在,因为它是 uint64
,所以只考虑前 64 位。
这仍然会导致溢出,因为左操作数 1
是非类型的并且可以包含大量位,返回的值对于 uint64
:
来说太大了
var j uint64 = 1<<uint64(66) - 1 // overflow. Note that uint64(64)
fmt.Println(j) // is typed, but it's still a constant
我正在查看 A Tour of Go,但我对他们的基本 types.go 示例中的某些内容感到困惑:
MaxInt uint64 = 1<<64 - 1
在无符号 64 位整数中向左移动 1 64 个位置不应该导致溢出(a.k.a。移动超过 MSB 位)?
但是,直到该行更改为:
,编译器才会报错MaxInt uint64 = 1<<65 - 1
./basic-types.go:5: constant 36893488147419103231 overflows uint64
如果我编写一些代码来迭代不同长度的左移,包括如上例所示的导致编译器呕吐的 65 位移位,我会看到两件事:
它的行为符合我的预期,因为
1<<63
将 1 放在 uint64 可能的 MSB 中
不会溢出了(咦?!?!)
代码:
package main
import "fmt"
func main() {
for i := 60; i < 66; i++ {
var j uint64 = 1 << uint64(i) - 1
fmt.Printf("%2d | %64b | %#18x\n", i, j, j)
}
输出:
60 | 111111111111111111111111111111111111111111111111111111111111 | 0xfffffffffffffff
61 | 1111111111111111111111111111111111111111111111111111111111111 | 0x1fffffffffffffff
62 | 11111111111111111111111111111111111111111111111111111111111111 | 0x3fffffffffffffff
63 | 111111111111111111111111111111111111111111111111111111111111111 | 0x7fffffffffffffff
64 | 1111111111111111111111111111111111111111111111111111111111111111 | 0xffffffffffffffff
65 | 1111111111111111111111111111111111111111111111111111111111111111 | 0xffffffffffffffff
写的时候
1<<64
上面的 1
不是 int64
。它是一个常量文字。来自语言规范:
Constant expressions are always evaluated exactly; intermediate values and the constants themselves may require precision significantly larger than supported by any predeclared type in the language.
因此,常量文字是在编译时计算的,并且可能非常大,因为它不是语言实现的特定类型。
下面实际上会给出溢出错误:
var i int64
i = 1<<65 - 1
因为现在常量文字表达式的计算结果大于 int64
可以包含的值。
阅读更多相关信息 here。
要了解为什么您的示例代码适用于 i = 65
,请参阅 Golang specs 中的以下规范:
The right operand in a shift expression must have unsigned integer type or be an untyped constant that can be converted to unsigned integer type. If the left operand of a non-constant shift expression is an untyped constant, it is first converted to the type it would assume if the shift expression were replaced by its left operand alone.
上面的粗体部分与您的代码有关。考虑以下代码:
a := 66
var j uint64 = 1<<uint64(a) - 1
此处在移位运算符中,右操作数是一个非常量表达式。所以整个移位运算就变成了非常量移位表达式。因此,如上所述,左操作数 1
被转换为 uint64
.
现在,正在 uint64(1)
上进行转换,可以使用 <<
将其转换到任意多个位置。您可以将它移动到 64 位以外,并且实现很容易允许它。但在这种情况下,上面保存 uint64(1)
的内存将包含所有零。
请注意,根据语言规范,此行为与溢出不同。同样,只要正确的运算符不是常量表达式,语言实现就允许进行尽可能多的转换。因此,例如,这将起作用:
a := 6666
var j uint64 = 1<<uint64(a) - 1 // non-constant shift expression
这样想。早些时候,1
是无类型的。它具有任意精度(取决于实现)并且返回整数(所有位)。现在,因为它是 uint64
,所以只考虑前 64 位。
这仍然会导致溢出,因为左操作数 1
是非类型的并且可以包含大量位,返回的值对于 uint64
:
var j uint64 = 1<<uint64(66) - 1 // overflow. Note that uint64(64)
fmt.Println(j) // is typed, but it's still a constant