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
", v)
fmt.Printf("Source hex: %4x
", uint16(v))
fmt.Printf("Result hex: %4x
", uint8(int8(v)))
fmt.Printf("Result : %4v
", 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