Golang位移运算符转换

3
我无法理解在golang中如何通过1<<s来返回0,如果var s uint = 33。但是1<<33会返回8589934592。 如何将移位运算符转换为值为0。
我正在阅读语言规范并卡在这个部分: https://golang.org/ref/spec#Operators 具体来说,是文档中的这一段:
“移位表达式中的右操作数必须具有无符号整数类型或者是可由uint类型的值表示的无类型常量。如果非常量移位表达式的左操作数是无类型常量,则首先会将其隐式转换为它将假定的类型,即左操作数本身。”
官方Golang文档中的一些示例:
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

更新:

另一个非常相关的问题,附带示例:

package main

import (
    "fmt"
)

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

这个程序返回 -16

当将 int16 转换为 int8 时,数字 4336 如何变成 -16 呢?


我在例子中进行了更正。 - Web Steps
在这种情况下,答案是“因为符号位”。我现在无法引用规格,但显然这个转换是通过丢弃高字节并将其余部分解释为带符号的8位整数来完成的。带符号整数有一个称为符号位的东西。如果这个位是1,那么这个数字是负数。 - Sergio Tulentsev
非常有用的信息。它可能会帮助我理解。所以4336的二进制是1000011110000,16是10000。-16是110000吗?符号位保留2位,那么int8剩下的6位会导致将数字1000011110000截断为10000(即16)吗? - Web Steps
2个回答

5
如果您有这个:
var s uint = 33
fmt.Println(1 << s)

然后引用部分适用:
如果非常量移位表达式的左操作数是无类型常量,则首先将其隐式转换为如果移位表达式仅由其左操作数替换时它将承担的类型。
因为`s`不是常量(而是变量),所以`1 >> s`是一个非常量移位表达式。左操作数是`1`,它是一个无类型常量(例如`int(1)`是一个有类型常量),因此它被转换为它在表达式中仅为`1`而不是`1 << s`时获取的类型。
fmt.Println(1)

在上述代码中,未定义类型的常量1会被转换为int类型,因为这是其默认类型。无类型常量的默认类型定义在规范:常量中:

无类型常量具有一个默认类型,即在需要类型化值的上下文中隐式转换常量的类型,例如,在没有显式类型的情况下进行短变量声明时,如i := 0。无类型常量的默认类型分别为boolruneintfloat64complex128string,具体取决于它是布尔、rune字符、整数、浮点、复数还是字符串常量。

而上面的结果与架构相关。如果int为32位,则结果为0。如果int为64位,则结果为8589934592(因为将一个1位左移33次将使其移出32位int数字)。
在Go Playground中,int的大小为32位(4个字节)。可以参考下面的示例:
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)

上面的输出(在Go Playground上尝试):
int size: 4
0
0
8589934592

如果我在我的64位计算机上运行以上应用程序,输出结果为:
int size: 8
8589934592
0
8589934592

另请参见Go Blog:常量了解常量在Go中的工作原理。
请注意,如果您编写1 << 33,那不是一个非常量移位表达式,你的引用适用于“非常量移位表达式”的左操作数。 1 << 33是一个常量移位表达式,在“常量空间”中计算,并且结果将转换为int类型,它不能适应32位int,因此会产生编译时错误。它可以使用变量,因为变量可能会溢出。常量不会溢出:

数字常量表示任意精度的精确值,不会溢出。

请参见如何在常量上执行算术运算? 更新: 回答您的补充:从int16转换为int8仅保留最低的8位。整数使用二进制补码格式表示,其中如果数字为负,则最高位为1
这在规范:转换中详细说明:

在整数类型之间进行转换时,如果值是带符号整数,则将其符号扩展为隐式无限精度;否则它将被零扩展。然后将其截断以适合结果类型的大小。例如,如果v := uint16(0x10F0),那么uint32(int8(v)) == 0xFFFFFFF0。转换始终产生有效值;没有溢出的迹象。

因此,当您将int16值转换为int8时,如果源数字在位7(第8位)处有一个1,则结果将为负数,即使源不是负数。同样,如果源在位7处为0,则结果将为正数,即使源为负数。
请参见以下示例:
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()
}

输出结果(请在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

请参考相关问题:

将int64转换为uint64时,符号是否保留?

将64位整数-1格式化为十六进制,在Golang和C之间存在差异


感谢提供的详细信息。但是在 Google Playground 上,如果输入 fmt.Println(1<<33),它会给出 int 的溢出错误。但是 fmt.Println(1 << s) 返回 0。我可以尝试 fmt.Println(int32(1)<<33) 或 fmt.Println(int32(1<<33)),但它们总是返回溢出错误。有些东西我无法理解。 - Web Steps
1
@Salimd83 如果你写 1 << 33,那不是同样的情况,这不是一个非常量移位表达式,你引用的是:“非常量移位表达式的左操作数”。1<<33 是一个常量移位表达式,在“常量空间”中计算,结果将被转换为 int,而这个值无法适应32位的 int。它可以使用变量,因为变量可以溢出。常量不会溢出。 - icza

2
你正在32位模式下构建和运行程序(go playground?)。 在其中,int是32位宽度,并且与int32相同。最初的回答。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接