为什么在golang中不会发生64位溢出? [英] Why doesn't left bit shifting by 64 overflow in golang?

查看:850
本文介绍了为什么在golang中不会发生64位溢出?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在看一看 Go Tour of Go ,我很困惑他们的东西basic-types.go示例:

  MaxInt uint64 = 1 <<64  -  1 

不应该在无符号的64位整数中将一个1 64个位置移到左边导致溢出(也就是将位移过MSB)?



然而,编译器不会抱怨,直到该行被更改为:

  MaxInt uint64 = 1 << 65  -  1 

./basic-types.go:5:常量36893488147419103231溢出uint64

如果我编写一些代码来遍历不同长度的左移,包括像上例那样移动65来导致编译器出错,我会看到两件事: / p>


  1. 它的行为如我所料,在 1 <63 lt 将1放置在MSB中可能为uint64


  2. 它不会

code:

  package main 

importfmt

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

}

输出:

  60 | 11111111111111111111111111111111111111111111111111111111111111 | 0xfffffffffffffff 
61 | 111111111111111111111111111111111111111111111111111111111111111 | 0x1fffffffffffffff
62 | 11111111111111111111111111111111111111111111111111111111111111 | 0x3fffffffffffffff
63 | 111111111111111111111111111111111111111111111111111111111111111 | 0x7fffffffffffffff
64 | 1111111111111111111111111111111111111111111111111111111111111111 | 0xffffffffffffffff
65 | 1111111111111111111111111111111111111111111111111111111111111111 | 0xffffffffffffffff


解决方案

b

  1 <64 

上面的 1 不是 int64 。它是一个常量字面。从语言规范:


常量表达式总是精确计算;中间值
和常量本身可能需要的精度显着大于语言中任何预先声明类型支持的



<因此,一个常量字面值在编译时被评估,并且可能非常大,因为它不是语言实现的特定类型。



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

  var i int64 
i = 1 <<65 - 1
int64
的值包含。



详细了解此



要知道您的示例代码为什么适用于 i = 65 ,请参阅Golang 规格


正确的操作数i n移位表达式必须具有无符号整数
类型或者是可以转换为无符号
整数类型的无类型常量。如果非常量移位表达式
左操作数是一个无类型常量,则它首先被转换为
所假设的类型,如果移位表达式被左操作数$ b $替换b单独使用


上面的方法与您的代码有关。考虑下面的代码:

  a:= 66 
var j uint64 = 1

在shift运算符中,右操作数是非常量exrpession EM>。所以整个移位操作变成了非常量移位表达式。因此,如上所述,左操作数 1 被转换为 uint64



现在,正在对 uint64(1)进行转换,可以使用<< / code>到任意位置。你可以将它转移到64位以上,并且实现很容易。但在这种情况下,上面持有 uint64(1)的内存将包含所有的零。



请注意,这行为与语言规范中的溢出不同。同样,只要正确的操作符不是一个常量表达式,语言实现就允许多次移位。因此,例如,这将工作:

  a:= 6666 
var j uint64 = 1<

以这种方式思考它。早些时候, 1 是无类型的。它有一个任意的精度(取决于实现),整个数字被返回(所有的位)。现在,由于它是 uint64 ,所以只考虑前64位。



这仍然是原因因为左操作数 1 是无类型的并且可以包含大量的位,所以返回一个值太大的 uint64

  var j uint64 = 1  fmt.Println(j)//是键入的,但它仍然是一个常量


I was taking a look at A Tour of Go and I was confused by something in their basic-types.go example:

MaxInt uint64     = 1<<64 - 1

Shouldn't shifting a 1 64 positions to the left in an unsigned 64 bit integer cause overflow (a.k.a. shifting a bit past the MSB)?

However, the compiler doesn't complain until the line is changed to:

MaxInt uint64     = 1<<65 - 1

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

If I write some code to iterate over left shifts of varying lengths, including shifting by 65 as in the above example that causes the compiler to barf, I see two things:

  1. It behaves as I expected, in that 1<<63 places the 1 in the MSB possible for an uint64

  2. It doesn't overflow anymore (huh?!?!)

code:

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)

    }

output:

60 |     111111111111111111111111111111111111111111111111111111111111 |  0xfffffffffffffff
61 |    1111111111111111111111111111111111111111111111111111111111111 | 0x1fffffffffffffff
62 |   11111111111111111111111111111111111111111111111111111111111111 | 0x3fffffffffffffff
63 |  111111111111111111111111111111111111111111111111111111111111111 | 0x7fffffffffffffff
64 | 1111111111111111111111111111111111111111111111111111111111111111 | 0xffffffffffffffff
65 | 1111111111111111111111111111111111111111111111111111111111111111 | 0xffffffffffffffff

解决方案

When you write

1<<64

The 1 above is not an int64. It is a constant literal. From the language specs:

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.

Thus, a constant literal is evaluated at compile time and can be very large because it is not a specific type of the language implementation.

Below will in fact give an overflow error:

var i int64
i = 1<<65 - 1

Because now the constant literal expression evaluates to a value greater than that an int64 can contain.

Read more about this here.

To know why your example code works for i = 65, refer to the below specification from the 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.

The blod part above concerns your code. Consider the code below:

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

Here in the shift operator, the right operand is a non-constant exrpession. So the whole shift operation becomes non-constant shift expression. Thus, as described above, the left operand, 1 is converted to a uint64.

Now, the shift is being done on uint64(1), which can be shifted using << to as many places as you want. You can shift it beyond 64 bits, and the implementation will easily allow it. But in this case the memory that's holding the uint64(1) above will contain all zeros.

Note that this behavior is not the same thing as an overflow as per the language specifications. Again, the language implemntation allows for as many shifts as long as the right operator is not a constant expression. So for example, this will work:

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

Think about it this way. Earlier, the 1 was untyped. It had an arbitrary precision (implementation dependent) and the whole number was being returned (all the bits). Now, since it is a uint64, only the first 64 bits are being taken into consideration.

This still causes an overflow because the left operand 1 is untypes and can contain a large number of bits, returning a value too large for 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

这篇关于为什么在golang中不会发生64位溢出?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆