3

I can not understand in golang how 1<<s return 0 if var s uint = 33. But 1<<33 return 8589934592. How a shift operator conversion end up with a value of 0.

I'm reading the language specification and stuck in this section: https://golang.org/ref/spec#Operators

Specifically this paragraph from docs:

"The right operand in a shift expression must have unsigned integer type or be an untyped constant representable by a value of type uint. If the left operand of a non-constant shift expression is an untyped constant, it is first implicitly converted to the type it would assume if the shift expression were replaced by its left operand alone."

Some example from official Golang docs:

var s uint = 33
var i = 1<<s                  // 1 has type int
var j int32 = 1<<s            // 1 has type int32; j == 0
var k = uint64(1<<s)          // 1 has type uint64; k == 1<<33

Update:

Another very related question, with an example:

package main

import (
    "fmt"
)

func main() {
v := int16(4336)
    fmt.Println(int8(v))
}

This program return -16

How does the number 4336 become -16 in converting int16 to int8

Axifive
  • 1,159
  • 2
  • 19
  • 31
Web Steps
  • 334
  • 2
  • 11
  • I corrected it in the example – Web Steps Mar 05 '19 at 12:37
  • In this case, the answer is "because sign bit". I can't quote the spec at the moment, but apparently this conversion is done by discarding the higher byte and interpreting the rest as a signed 8-bit int. Signed integers have a thing called sign bit. If this bit is 1, the number is negative. – Sergio Tulentsev Mar 05 '19 at 12:41
  • very helpful info. it may help me get it. So 4336 in binary is 1000011110000 and 16 is 10000. -16 is 110000? does sign reserve 2 bits, so what left of int8 is 6 bits and it result in truncating the number 1000011110000 to 10000 (which is 16)? – Web Steps Mar 05 '19 at 13:05

2 Answers2

5

If you have this:

var s uint = 33
fmt.Println(1 << s)

Then the quoted part applies:

If the left operand of a non-constant shift expression is an untyped constant, it is first implicitly converted to the type it would assume if the shift expression were replaced by its left operand alone.

Because s is not a constant (it's a variable), therefore 1 >> s is a non-constant shift expression. And the left operand is 1 which is an untyped constant (e.g. int(1) would be a typed constant), so it is converted to a type that it would get if the expression would be simply 1 instead of 1 << s:

fmt.Println(1)

In the above, the untyped constant 1 would be converted to int, because that is its default type. Default type of constants is in Spec: Constants:

An untyped constant has a default type which is the type to which the constant is implicitly converted in contexts where a typed value is required, for instance, in a short variable declaration such as i := 0 where there is no explicit type. The default type of an untyped constant is bool, rune, int, float64, complex128 or string respectively, depending on whether it is a boolean, rune, integer, floating-point, complex, or string constant.

And the result of the above is architecture dependent. If int is 32 bits, it will be 0. If int is 64 bits, it will be 8589934592 (because shifting a 1 bit 33 times will shift it out of a 32-bit int number).

On the Go playground, size of int is 32 bits (4 bytes). See this example:

fmt.Println("int size:", unsafe.Sizeof(int(0)))

var s uint = 33

fmt.Println(1 << s)
fmt.Println(int32(1) << s)
fmt.Println(int64(1) << s)

The above outputs (try it on the Go Playground):

int size: 4
0
0
8589934592

If I run the above app on my 64-bit computer, the output is:

int size: 8
8589934592
0
8589934592

Also see The Go Blog: Constants for how constants work in Go.

Note that if you write 1 << 33, that is not the same, that is not a non-constant shift expression, which your quote applies to: "the left operand of a non-constant shift expression". 1<<33 is a constant shift expression, which is evaluated at "constant space", and the result would be converted to int which does not fit into a 32-bit int, hence the compile-time error. It works with variables, because variables can overflow. Constants do not overflow:

Numeric constants represent exact values of arbitrary precision and do not overflow.

See How does Go perform arithmetic on constants?

Update:

Answering your addition: converting from int16 to int8 simply keeps the lowest 8 bits. And integers are represented using the 2's complement format, where the highest bit is 1 if the number is negative.

This is detailed in Spec: Conversions:

When converting between integer types, if the value is a signed integer, it is sign extended to implicit infinite precision; otherwise it is zero extended. It is then truncated to fit in the result type's size. For example, if v := uint16(0x10F0), then uint32(int8(v)) == 0xFFFFFFF0. The conversion always yields a valid value; there is no indication of overflow.

So when you convert a int16 value to int8, if source number has a 1 in bit position 7 (8th bit), the result will be negative, even if the source wasn't negative. Similarly, if the source has 0 at bit position 7, the result will be positive, even if the source is negative.

See this example:

for _, v := range []int16{4336, -129, 8079} {
    fmt.Printf("Source    : %v\n", v)
    fmt.Printf("Source hex: %4x\n", uint16(v))
    fmt.Printf("Result hex: %4x\n", uint8(int8(v)))
    fmt.Printf("Result    : %4v\n", uint8(int8(v)))
    fmt.Println()
}

Output (try it on the Go Playground):

Source    : 4336
Source hex: 10f0
Result hex:   f0
Result    :  -16

Source    : -129
Source hex: ff7f
Result hex:   7f
Result    :  127

Source    : 8079
Source hex: 1f8f
Result hex:   8f
Result    : -113

See related questions:

When casting an int64 to uint64, is the sign retained?

Format printing the 64bit integer -1 as hexadecimal deviates between golang and C

icza
  • 389,944
  • 63
  • 907
  • 827
  • Thank for the details. But on google play ground if it typed fmt.Println(1<<33) it gives overflow error for int. But fmt.Println(1 << s) return 0. I can do fmt.Println(int32(1)<<33) or fmt.Println(int32(1<<33)) it always return overflow error. Something im not able to understand – Web Steps Mar 05 '19 at 09:13
  • 1
    @Salimd83 If you write `1 << 33`, that is not the same, that is not a non-constant shift expression, which your quote applies to: _"the left operand of a non-constant shift expression"_. `1<<33` is a constant shift expression, which is evaluated at "constant space", and the result would be converted to `int` which does not fit into a 32-bit `int`. It works with variables, because variables can overflow. Constants do not overflow. – icza Mar 05 '19 at 09:23
2

You're building and running the program in 32bit mode (go playground?). In it, int is 32-bit wide and behaves the same as int32.

Sergio Tulentsev
  • 226,338
  • 43
  • 373
  • 367