我一直在阅读post on constants in Go,我正在尝试了解它们是如何在内存中存储和使用的。您可以在Go中对非常大的常量执行操作,只要结果适合内存,您就可以将该结果强制转换为类型。例如,此代码会打印10
,如您所料:
const Huge = 1e1000
fmt.Println(Huge / 1e999)
这是如何在幕后工作的?在某些时候,Go必须将1e1000
和1e999
存储在内存中,以便对它们执行操作。那么常量如何存储,以及Go如何对它们进行算术运算?
答案 0 :(得分:5)
简短摘要(TL; DR)就在答案的最后。
未定型的任意精度常量不会在运行时存在,常量仅在编译时生效(在编译期间)。话虽如此,Go不必在运行时以任意精度表示常量,仅在编译应用程序时。
为什么呢?因为常量不会被编译到可执行二进制文件中。他们不必是。让我们举个例子:
const Huge = 1e1000
fmt.Println(Huge / 1e999)
源代码中有一个常量Huge
(并且将在包对象中),但它不会出现在您的可执行文件中。相反,对fmt.Println()
的函数调用将记录一个传递给它的值,其类型将为float64
。因此,在可执行文件中,只会记录float64
值为10.0
的{{1}}。可执行文件中没有任何数字为1e1000
的迹象。
此float64
类型派生自无类型常量Huge
的默认类型。 1e1000
是floating-point literal。验证它:
const Huge = 1e1000
x := Huge / 1e999
fmt.Printf("%T", x) // Prints float64
回到任意精度:
数字常量表示任意精度的精确值,不会溢出。
因此常量表示任意精度的精确值。正如我们所看到的,不需要在 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/big
和go/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
,它将失去较低的精度,但编译器将确定要使用的类型。