一尘不染

为什么在golang中没有左移64位溢出?

go

我正在看《A Tour of Go》,我对他们的basic-
types.go示例感到困惑:

MaxInt uint64     = 1<<64 - 1

难道不应该在无符号的64位整数中向左移动1 64个位置会导致溢出(也就是比MSB高一点)吗?

但是,直到该行更改为:编译器才会抱怨:

MaxInt uint64     = 1<<65 - 1

./basic-types.go:5: constant 36893488147419103231 overflows uint64

如果我编写一些代码以迭代不同长度的左移,包括如上例中那样导致导致编译器发声的左移65,我会看到两件事:

  1. 它的行为符合我的预期,1<<63因为它会将uint64中的1放置在MSB中

  2. 它不再溢出了(呵呵!!!!)

码:

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

阅读 390

收藏
2020-07-02

共1个答案

一尘不染

当你写

1<<64

1上面没有一个int64。这是一个 常量常量 。根据语言规格:

常量表达式总是精确地求值;中间值和常量本身可能需要比该语言中任何预声明类型支持的精度大得多的精度。

因此,常量文字会在编译时进行评估,因为它不是语言实现的特定类型,所以它可能非常大。

下面实际上会给出一个溢出错误:

var i int64
i = 1<<65 - 1

因为现在常量文字表达式求值的值大于int64可以包含的值。

在此处阅读有关此内容的更多信息。

要知道您的示例代码为何适用i = 65,请参考Golang
规范中的以下规范

移位表达式中的右操作数必须具有无符号整数类型,或者是可以转换为无符号整数类型的无类型常量。如果 非恒定移位表达式
左操作数是未类型化的常数,则首先将其转换为假定移位表达式被其左操作数单独替换时所假定的类型

上面的膨胀部分与您的代码有关。考虑下面的代码:

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。它具有任意精度(取决于实现),并且正在返回整数(所有位)。现在,由于它是a uint64,因此仅考虑了前64位。

这仍然会导致溢出,因为左操作数1是untypes,并且可能包含大量位,对于a返回的值太大uint64

var j uint64 = 1<<uint64(66) - 1 // overflow. Note that uint64(64)
fmt.Println(j)                   // is typed, but it's still a constant
2020-07-02