Go如何对常量执行算术运算?

时间:2016-08-16 18:43:06

标签: go const

我一直在阅读post on constants in Go,我正在尝试了解它们是如何在内存中存储和使用的。您可以在Go中对非常大的常量执行操作,只要结果适合内存,您就可以将该结果强制转换为类型。例如,此代码会打印10,如您所料:

const Huge = 1e1000
fmt.Println(Huge / 1e999)

这是如何在幕后工作的?在某些时候,Go必须将1e10001e999存储在内存中,以便对它们执行操作。那么常量如何存储,以及Go如何对它们进行算术运算?

2 个答案:

答案 0 :(得分:5)

简短摘要(TL; DR)就在答案的最后。

未定型的任意精度常量不会在运行时存在,常量仅在编译时生效(在编译期间)。话虽如此,Go不必在运行时以任意精度表示常量,仅在编译应用程序时。

为什么呢?因为常量不会被编译到可执行二进制文件中。他们不必是。让我们举个例子:

const Huge = 1e1000
fmt.Println(Huge / 1e999)

源代码中有一个常量Huge(并且将在包对象中),但它不会出现在您的可执行文件中。相反,对fmt.Println()的函数调用将记录一个传递给它的值,其类型将为float64。因此,在可执行文件中,只会记录float64值为10.0的{​​{1}}。可执行文件中没有任何数字为1e1000的迹象。

float64类型派生自无类型常量Huge默认类型。 1e1000floating-point literal。验证它:

const Huge = 1e1000
x := Huge / 1e999
fmt.Printf("%T", x) // Prints float64

回到任意精度:

Spec: Constants:

  

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

因此常量表示任意精度的精确值。正如我们所看到的,不需要在 runtime 中以任意精度表示常量,但编译器仍需要在编译时执行某些操作。它

显然"无限"精度无法处理。但是没有必要,因为源代码本身不是无限的" (源的大小是有限的)。但是,允许真正的任意精度是不切实际的。因此规范为编译器提供了一些自由:

  

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

     
      
  • 表示至少256位的整数常量。
  •   
  • 表示浮点常数,包括复数常量的部分,尾数至少为256位,有符号指数至少为32位。
  •   
  • 如果无法准确表示整数常量,则给出错误。
  •   
  • 如果由于溢出而无法表示浮点或复数常量,则给出错误。
  •   
  • 如果由于精度限制而无法表示浮点或复数常量,则舍入到最接近的可表示常量。   这些要求既适用于文字常量,也适用于评估constant expressions的结果。
  •   

但是,还要注意,当以上所述时,标准包为您提供了仍然使用"任意"表示和处理值(常量)的方法。精度,见包go/constant。您可以查看其来源以了解它是如何实施的。

实施在go/constant/value.go。表示此类值的类型:

// A Value represents the value of a Go constant.
type Value interface {
    // Kind returns the value kind.
    Kind() Kind

    // String returns a short, human-readable form of the value.
    // For numeric values, the result may be an approximation;
    // for String values the result may be a shortened string.
    // Use ExactString for a string representing a value exactly.
    String() string

    // ExactString returns an exact, printable form of the value.
    ExactString() string

    // Prevent external implementations.
    implementsValue()
}

type (
    unknownVal struct{}
    boolVal    bool
    stringVal  string
    int64Val   int64                    // Int values representable as an int64
    intVal     struct{ val *big.Int }   // Int values not representable as an int64
    ratVal     struct{ val *big.Rat }   // Float values representable as a fraction
    floatVal   struct{ val *big.Float } // Float values not representable as a fraction
    complexVal struct{ re, im Value }
)

如您所见,math/big包用于表示无类型的任意精度值。例如big.Int(来自math/big/int.go):

// An Int represents a signed multi-precision integer.
// The zero value for an Int represents the value 0.
type Int struct {
    neg bool // sign
    abs nat  // absolute value of the integer
}

nat的位置(来自math/big/nat.go):

// An unsigned integer x of the form
//
//   x = x[n-1]*_B^(n-1) + x[n-2]*_B^(n-2) + ... + x[1]*_B + x[0]
//
// with 0 <= x[i] < _B and 0 <= i < n is stored in a slice of length n,
// with the digits x[i] as the slice elements.
//
// A number is normalized if the slice contains no leading 0 digits.
// During arithmetic operations, denormalized values may occur but are
// always normalized before returning the final result. The normalized
// representation of 0 is the empty or nil slice (length = 0).
//
type nat []Word

最后Word是(来自math/big/arith.go

// A Word represents a single digit of a multi-precision unsigned integer.
type Word uintptr

<强>摘要

在运行时:预定义类型提供有限的精度,但您可以模仿&#34;某些包的任意精度,例如math/biggo/constant。在编译时:常量似乎提供任意精度,但实际上编译器可能无法实现这一点(不必);但是规范仍然为所有编译器必须支持的常量提供最小的精度,例如整数常量必须用至少256位表示,即32字节(与int64相比,即#34;仅#34; 8字节)。

当创建可执行二进制文件时,常量表达式(具有任意精度)的结果必须转换并用有限精度类型的值表示 - 这可能是不可能的,因此可能导致编译时错误。请注意,只有结果 - 不是中间操作数 - 必须转换为有限精度,以任意精度执行常量操作。

如何实现这种任意或增强的精度不是由规范math/big定义的,例如存储&#34;数字&#34;切片中的数字(其中数字不是基数10表示的数字,但是&#34;数字&#34;是uintptr,它类似于32位体系结构上的基础4294967295表示,甚至更大在64位架构上。)

答案 1 :(得分:0)

Go常量未分配给内存。它们在编译器的上下文中使用。你引用的博客文章给出了Pi的例子:

Pi    = 3.14159265358979323846264338327950288419716939937510582097494459

如果将Pi分配给float32,它将失去精度以适应,但如果将其分配给float64,它将失去较低的精度,但编译器将确定要使用的类型。