Why is there no 64 bit overflow offset left in golang?

I looked at the "Tour along the way" and I was confused by something in the basic-types.go example:

MaxInt uint64 = 1<<64 - 1 

Shouldn't you shift 1 64 positions to the left in an unsigned 64-bit integer overflow call (aka by moving the bit past the MSB)?

However, the compiler does not complain until the line changes 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 different lengths, including a shift of 65, as in the above example, which calls the compiler in barf, I see two things:

  • It behaves as I expected, since 1<<63 puts 1 in MSB for uint64

  • It no longer overflows (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 
+4
source share
1 answer

When you write

 1<<64 

1 above is not int64 . This is a constant literal. From language specifications:

Constant expressions are always evaluated exactly; intermediate values ​​and constants themselves can significantly increase accuracy more than is supported on any previous 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 language implementation.

The following is an overflow error:

 var i int64 i = 1<<65 - 1 

Because now the expression of constant literals evaluates the value more than int64 can contain.

Read more about it here .

To find out why your sample code works for i = 65 , refer to the specification below from the Golang specs:

The correct operand in the shift expression must be an unsigned integer type or be an untyped constant that can be converted to an unsigned integer type. If the left operand of a variable shift expression is an untyped constant, it is first converted to the type that it will assume that if the shift expression has been replaced with its left operand one .

Part of the block above relates to your code. Consider the following code:

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

Here in the shift operator, the right operand is a mutable exrpession. Thus, the entire switching operation becomes not a constant expression of the shift. Thus, as described above, the left operand 1 converted to uint64 .

Now the shift is performed on uint64(1) , which can be shifted with << to as many places as you want. You can move it beyond 64 bits, and implementation will easily allow it. But in this case, the memory containing uint64(1) above will contain all zeros.

Please note that this behavior is not the same as overflow according to language specifications. Again, the language implementation allows so many shifts until the right operator is a constant expression. So, for example, this will work:

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

Think of it this way. Previously 1 was untyped. It had arbitrary precision (implementation dependent) and the whole number (all bits) was returned. Now, since it is uint64 , only the first 64 bits are counted.

This still causes an overflow because the left operand 1 is untypes and may contain a large number of bits, returning a value too large for uint64 :

 var j uint64 = 1<<uint64(66) - 1 // overflow. Note that uint64(64) fmt.Println(j) // is typed, but it still a constant 
+9
source

Source: https://habr.com/ru/post/1264062/


All Articles