【问题标题】:Constant 1 truncated to integer?常量 1 截断为整数?
【发布时间】:2014-10-24 09:54:46
【问题描述】:

为什么这段代码无法编译?

package main
const a = 1.000001
const base = 0
const b = a+base
func main() {
    f(b)
}
func f(int) {}

$ go run a.go
# command-line-arguments
./a.go:4: constant 1 truncated to integer

是说1被截断了?或者那个 1 不能被截断?它在说哪一个?

有人回答上述代码无法编译,因为bfloat64。但是为什么会这样编译:

package main
import "fmt"
const a = 1.000001
const b = a-0.000001
func main() {
    fmt.Printf("%T %v\n",a,a)
    fmt.Printf("%T %v\n",b,b)
    f(b)
}
func f(int) {}

$ go run a.go 
float64 1.000001
float64 1

b 在这里是float64,但可以传递给f

【问题讨论】:

    标签: types go weak-typing


    【解决方案1】:

    你的第一个程序可以这样重写:

    package main
    func main() {
        f(1.000001)
    }
    func f(int) {}
    

    这显然不是将整数值传递给整数函数。

    您的第二个程序可以类似地重写如下:

    package main
    import "fmt"
    func main() {
        fmt.Printf("%T %v\n",1.000001,1.000001)
        fmt.Printf("%T %v\n",1,1)
        f(1)
    }
    func f(int) {}
    

    看起来不错。

    我所做的只是手动替换 ab 常量。这就是一切。

    【讨论】:

      【解决方案2】:

      Go 有 very strict conversion rules 用于常量:

      在以下任何一种情况下,常量值 x 都可以转换为类型 T:

      • x 可以用 T 类型的值表示。
      • x 是浮点常数,T 是浮点类型,x 可以用 IEEE 754 舍入后的 T 类型的值表示 舍入规则。常量T(x) 是四舍五入的值。
      • x 是整数常量,T 是字符串类型。与非常量 x 相同的规则适用于这种情况。

      golang blog post about constants 可能有助于进一步理解这一点。 由于严格性,违反引用规则的每个转换都被视为错误。这背后的原因是 Go 试图尽可能准确地表示常量。这也意味着最终类型是在使用的表达式的上下文中决定的。放弃精度会破坏这一点,并且可能是编程错误的迹象。

      如果你真的想将一个值四舍五入为整数,请将其转换为变量(Example on play):

      const b = 1.01
      c := b
      f(int(c))
      

      这是可行的,因为编译器不跟踪值的来源,并且常量规则不适用于变量。

      但是为什么当我把它改成这个时它会起作用呢? const a = 1.000001;const b = a-0.000001

      在本例中b 等于 1。1 可以表示为整数,因此不涉及舍入和信息丢失。因此这不是错误,因为它符合浮点值的转换规则(如前所述)。

      【讨论】:

        【解决方案3】:

        go 团队最近发了一个blog post about this,建议你阅读一下。

        来自介绍

        Go 是一种静态类型语言,它不允许执行以下操作 混合数字类型。您不能将 float64 添加到 int 甚至 int32 到一个 int。然而,写 1e6*time.Second 或 math.Exp(1) 或 甚至 1

        TLDR - Go 中的常量是无类型的。他们的类型只是在最后一刻才结晶。

        这解释了你上面的问题。给定

        func f(int) {}
        

        然后

        f(1) // ok
        f(1.000) // OK
        f(1.0E6) // OK
        f(1.0001) // BAD
        

        【讨论】:

        • 你是说f 得到 1.000001?在我看来,无类型常量 b 被截断为 1(因此错误“常量 1 被截断为整数”),这将使其可以表示为 int。如果我将b 更改为 1.2,则错误是“常量 1.2 被截断为整数”。很明显,这里正在发生更多事情。
        • 表示要编译,需要截断 1.000001。换句话说,无类型值 1.000001 不能用 int 类型表示。在您的第二个示例中,1.000001-0.000001 是值 1。您可以按照自己喜欢的方式编写它(0x1、1.0 等),但这不会改变它的含义。 1 可以用 int 表示,因此可以编译。
        • a = 1.000001 表示“将常量 1 截断为整数”。如果我将 ab 更改为 1.2,则错误是“常量 1.2 被截断为整数”。很明显,这里正在发生更多事情。在一种情况下,它会报告截断的版本,而在另一种情况下则不会。
        【解决方案4】:

        免责声明:我对 Go 没有任何经验,但以下答案基于与数据类型相关的一般原则。

        您的函数f 采用int 类型的输入参数,但您传递给它的实际值(即b)具有基于您的代码的浮点值。如错误消息所述,这将导致浮点值截断为整数值。

        我相信您可以通过更改函数签名以将浮点类型值作为输入参数来解决此问题,即

        func f(float64) {}
        

        Demo in Go

        要将其与我熟悉的语言(C#)进行比较,您可以查看以下代码:

        public static void Main(string[] args)
            {
                var a = 1.3;
                var b = 1.3 + 9;
                f(b);
                Console.WriteLine("Hello, world!");
            }
        
        public static void f(int a)
            {
            }
        

        使用var 关键字,我们不会显式创建ab 数据类型为double 的变量。然而,由于浮点值被分配给它们,它们的类型被推断为double。现在,如果您将方法f 定义为采用数据类型int 的输入参数,然后传入ab。它会给你一个错误。但是,如果您将方法更改为采用 double 值而不是 int,您的代码将毫无问题地编译。

        Demo in C#

        【讨论】:

        • 但是为什么当我把它改成这个时它会起作用呢? const a = 1.000001;const b = a-0.000001
        • 再说一次,我没有 Go 的经验,所以你应该得到第二个意见,但我相信 1.000001 - 0.000001 = 1.000000 被视为整数 1,它满足 int 参数要求。
        • 有趣的是,reflect.TypeOf(b) 显示 float64,但 PrintLn(b) 显示 1,reflect.TypeOf(1) 显示 int
        • @shree.pat18 这是因为常量值的计算涉及到浮点数所以内部表示仍然是float64。但是结果是完全可以转换的,所以这里没有错误。
        猜你喜欢
        • 1970-01-01
        • 2012-11-19
        • 2017-11-29
        • 2013-12-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-28
        相关资源
        最近更新 更多