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

1
MaxInt uint64     = 1<<64 - 1

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

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

1
2
3
MaxInt uint64     = 1<<65 - 1

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

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

  • 它的行为符合我的预期,因为1<<63将1放入uint64的MSB中

  • 它不再溢出了(呵呵!!!!)

  • 码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package main

    import"fmt"

    func main() {
        for i := 60; i < 66; i++ {
            var j uint64 = 1 << uint64(i) - 1
            fmt.Printf("%2d | %64b | %#18x\
    ", i, j, j)

        }

    输出:

    1
    2
    3
    4
    5
    6
    60 |     111111111111111111111111111111111111111111111111111111111111 |  0xfffffffffffffff
    61 |    1111111111111111111111111111111111111111111111111111111111111 | 0x1fffffffffffffff
    62 |   11111111111111111111111111111111111111111111111111111111111111 | 0x3fffffffffffffff
    63 |  111111111111111111111111111111111111111111111111111111111111111 | 0x7fffffffffffffff
    64 | 1111111111111111111111111111111111111111111111111111111111111111 | 0xffffffffffffffff
    65 | 1111111111111111111111111111111111111111111111111111111111111111 | 0xffffffffffffffff

    当你写

    1
    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.

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

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

    1
    2
    var i int64
    i = 1<<65 - 1

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

    在这里阅读更多有关此的内容。

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

    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.

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

    1
    2
    a := 66
    var j uint64 = 1<<uint64(a) - 1

    在移位运算符中,右操作数是一个非恒定的表达式。因此整个移位操作成为非恒定移位表达式。因此,如上所述,左操作数1被转换为uint64

    现在,在uint64(1)上进行了移位,可以使用<<将其移位到任意数量的位置。您可以将其移位到64位以上,并且实现将很容易地允许它。但是在这种情况下,保存上述uint64(1)的内存将包含全零。

    请注意,根据语言规范,此行为与溢出并不相同。同样,只要正确的运算符不是一个常量表达式,语言限制就可以进行任意多的移位。因此,例如,这将起作用:

    1
    2
    a := 6666
    var j uint64 = 1<<uint64(a) - 1 // non-constant shift expression

    这样想吧。以前,1是未键入的。它具有任意精度(取决于实现),并且正在返回整数(所有位)。现在,由于它是uint64,因此仅考虑前64位。

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

    1
    2
    var j uint64 = 1<<uint64(66) - 1 // overflow. Note that uint64(64)
    fmt.Println(j)                   // is typed, but it's still a constant
    • 很有意思,因此在MaxInt uint64 = 1<<64 - 1中,1<<64在编译时以无类型的方式求值到18446744073709551616,然后在考虑到- 1时,最终将其放入uint64中。
    • 那么为什么当i = 65时循环中不会溢出? var j uint64 = 1 << uint64(i) - 1
    • 添加了解释。
    • 有趣; 感谢那! 自从Ive必须执行按位运算以来已经有很长时间了,那时它在FPGA和ASIC中。 从我的实现角度来看,这是有意义的,它是一个不错的小功能,可以在编译时对常量文字进行任意精度的计算,因此您可以确切地知道常量的含义。