关于 golang 除法运算符/的疑惑

2020-06-29 17:47:41 +08:00
 wangbenjun5
无意间发现的一个问题,举个栗子:

10/4 = 2
10/4.0 = 2.5
10.0/4 = 2.5

上面这个 2 个都可以理解,和大 PHP 一样,只要有一方是浮点型结果就是浮点型。

重点来了,如果被除的数不是字面量值,而是一个变量,比如

var ten = 10
ten/4.0 = 2

这个结果就有点奇怪了,有人说这是因为在 golang 里面结果取决于被除数。但是为毛使用字面量的时候就不一样了呢?
6147 次点击
所在节点    程序员
11 条回复
XanderChen
2020-06-29 18:02:33 +08:00
我怎么记得不加类型注解要用 := 来声明变量来着。

另外你把 10 改成 10.0 试试,没准是取决于...emmm....
因为 10 被隐式声明为整型,你又没声明你需要的数据类型,所以编译器决定给你整型结果?

要不你把 4.0 也做个变量再除一下试试。
rrfeng
2020-06-29 18:04:29 +08:00
字面量会进行类型推断,变量不需要类型推断,算式中的字面量直接按变量类型来,否则就得做强制类型转换。

大概就是这么回事。
nidaye999
2020-06-29 18:09:22 +08:00
浮点数不能给整数运算。
lxy42
2020-06-29 18:13:02 +08:00
你试试 ten / 4.1 大概就知道了
si
2020-06-29 18:15:51 +08:00
这种问题直接贴测试代码,大佬一看就清楚了。
wangbenjun5
2020-06-29 18:28:01 +08:00
我好奇的是为什么这么设计,前后有点矛盾,如果说取决于被除数那 10/4.0 也应该是 2,而不是 2.5 。

唯一能解释通的是,编译器做了类型推断,因为除数是浮点型,所有结果也是浮点数。
secondwtq
2020-06-29 19:03:45 +08:00
随手看了一下 spec,你这个事 constant expression,有这么一段:

> Any other operation on untyped constants results in an untyped constant of the same kind; that is, a boolean, integer, floating-point, complex, or string constant. If the untyped operands of a binary operation (other than a shift) are of different kinds, the result is of the operand's kind that appears later in this list: integer, rune, floating-point, complex. For example, an untyped integer constant divided by an untyped complex constant yields an untyped complex constant.

大概是为了 大道至简 把 C 里面的类型提升给整过来了。
favourstreet
2020-06-29 19:12:40 +08:00
@secondwtq 我觉得是反了,压根没整过来才出这种问题。Numeric types 一节里有这样一句话:
> Explicit conversions are required when different numeric types are mixed in an expression or assignment.
意思是必须手动转换类型,所以 C 里面行为明确的类型提升,go 里面变成未定义行为了……,而且按照常量表达式里的那句话,2.5/5 应该等于 0,实际上等于 0.5,这算什么?

所以一个语言有个 ISO 标准的好处就在这里……
secondwtq
2020-06-29 19:29:15 +08:00
@favourstreet 我觉得设计者的意思是,为了避免类型提升可能存在的问题,于是甩掉了 general 的类型提升,然后为了一点小方便,在 constant expression 这个 special case 里面允许这么做。可能是以为这样不仅 大道至简,还能避免类型提升可能的问题吧(至于类型提升这个东西自身到底应不应该存在?这个问题我推测他们并没有想过)。
结果就出了楼主这个问题。

> 而且按照常量表达式里的那句话,2.5/5 应该等于 0,实际上等于 0.5,这算什么?
我不知道你是怎么看出来的,他还举了例子,integer constant / complex constant = complex constant 。
favourstreet
2020-06-29 19:49:35 +08:00
@secondwtq 我确实饿极了脑子停转弄错了……我没用过 go 所以去官网上玩了一下 Try Go 发现能重现楼主提到的问题。我另外发现,不同类型变量之间的运算会报编译错误,根据语法还有这些错误提供的线索,我猜编译器可能是这样想的:
ten/4.0 这个表达式里,4.0 是一个常量,神奇的是,常量可以没有类型( untyped )!于是编译器认为转换成整型没有损失信息,还免得编译失败,于是开开心心转成整形了。
katsusan
2020-06-30 15:31:30 +08:00
golang 的编译大概分为三步,
#1.进行词法分析和语法分析,构建出语法树 AST
#2.进行类型检测 typecheck 和 AST 优化(var x = 10/4.0 就在这步计算出来)
#3.进行静态单赋值 SSA 优化(ten/4.0 在这步计算得出)和中间代码生成

解析 10/4.0 时,经过一系列 switch case 后会调用[evconst]( https://github.com/golang/go/blob/master/src/cmd/compile/internal/gc/const.go#L569)计算出 10/4.0 写到 x 节点上,而 evaconst 会先调用[match 函数]( https://github.com/golang/go/blob/master/src/cmd/compile/internal/gc/const.go#L686)
来将两个变量转化为同样类型,这个函数逻辑对应着#7 所记录的 spec 规范。

而碰到 print(ten/4.0)时由于左边为变量已经推导出为 int 型所以会被[defaultlit2 函数]( https://github.com/golang/go/blob/master/src/cmd/compile/internal/gc/const.go#L1126)获取并且按左节点优先原则将右边的 4.0 转化为左边的类型。

可以从 GOSSAFUNC=main go build x.go 获得的 html 里看到 main 函数从 source->AST->SSA 的转换过程。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/685694

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX