误解Go语言关于浮点舍入的规范



Go语言规范中关于常量表达式部分的说明:

编译器在计算无类型浮点或复杂常量表达式时可能使用舍入;请参阅常量小节中的实现限制。这种舍入可能导致浮点常量表达式在整数上下文中无效,即使它在使用无限精度计算时是整数,反之亦然。


句子

这种舍入可能导致浮点常量表达式在整数上下文中无效

指向如下内容:

func main() {
    a := 853784574674.23846278367
    fmt.Println(int8(a)) // output: 0
}

规范中引用的部分不适用于您的示例,因为a不是常量表达式而是变量,因此int8(a)正在转换非常量表达式。此转换由Spec:转换,数值类型之间的转换:

当将浮点数转换为整数时,分数被丢弃(截断为零)。

[…在所有涉及浮点数或复数值的非常量转换中,如果结果类型不能表示该值,则转换成功,但结果值依赖于实现。

由于您将非常量表达式a转换为853784574674.23846278367,因此小数部分被丢弃,并且由于结果不适合int8,因此不指定结果,它依赖于实现。

加引号的部分表示,虽然常量的表示精度比内置类型高得多(例如:float64int64),编译器(必须)实现的精度不是无限的(出于实际原因),即使浮点文字可以精确地表示,对它们执行操作也可能使用中间四舍五入,并且可能不会给出数学上正确的结果。

规格包括最小支持精度:

实现限制:尽管数字常量在语言中具有任意精度,但编译器可以使用具有有限精度的内部表示来实现它们。也就是说,每个实现必须:

  • 表示至少256位的整数常量。
  • 表示浮点常量,包括复数常量的部分,尾数至少为256位,有符号二进制指数至少为16位。
  • 如果不能精确地表示整数常量,则给出错误。
  • 如果由于溢出而无法表示浮点数或复杂常数,则给出错误。
  • 如果由于精度限制而无法表示浮点数或复数常数,则舍入到最接近的可表示常数。

例如:

const (
    x = 1e100000 + 1
    y = 1e100000
)
func main() {
    fmt.Println(x - y)
}

这段代码应该输出1,因为xy大1。在Go Playground上运行它会输出0,因为常量表达式x - y是带舍入执行的,因此+1会丢失。xy都是整数(没有分数部分),所以在整数上下文中结果应该是1。但是数字是1e100000,表示它需要大约333000位,这不是编译器的有效要求(根据规范,256位尾号就足够了)。

如果我们降低常数,我们得到正确的结果:

const (
    x = 1e1000 + 1
    y = 1e1000
)
func main() {
    fmt.Println(x - y)
}

输出数学上正确的1结果。在Go Playground上试试。表示数字1e1000需要大约~3333位,这似乎是支持的(并且远远高于最低256位要求)。

int8是一个有符号整数,取值范围是-128到127。这就是为什么您在int8(a)中看到意想不到的值的原因。转换。

最新更新