我一直在阅读有关Go中的常量的文章,并且试图了解它们如何在内存中存储和使用。您可以在Go中对非常大的常量执行运算,并且只要结果适合内存,就可以将结果强制为类型。例如,10如您所料,此代码显示:
10
const Huge = 1e1000 fmt.Println(Huge / 1e999)
这是如何工作的?在某个时候,Go必须存储1e1000并存储1e999在内存中,以便对其执行操作。那么常量是如何存储的,Go如何对其进行算术运算呢?
1e1000
1e999
简短摘要(TL; DR)在答案的结尾。
无类型的任意精度常量在运行时不存在,常量仅在编译时(在编译期间)存在。话虽如此,Go不必在运行时以任意精度表示常量,而仅在编译应用程序时。
为什么?因为常量不会被编译到可执行二进制文件中。他们不必是。让我们举个例子:
有一个不变Huge的 源代码 (并会在包对象),但它不会出现在你的可执行文件。相反,fmt.Println()将记录一个传递给它的值(类型为)来记录对函数的调用float64。因此在可执行文件中,只会记录一个float64值10.0。1e1000可执行文件中没有任何数字的迹象。
Huge
fmt.Println()
float64
10.0
此float64类型派生自 无类型 常量的 默认 类型。是浮点文字。验证: __Huge``1e1000
Huge``1e1000
const Huge = 1e1000 x := Huge / 1e999 fmt.Printf("%T", x) // Prints float64
回到任意精度:
规格:常数:
数字常数表示任意精度的精确值,并且不会溢出。
因此,常数表示任意精度的精确值。如我们所见,无需在 运行时 以任意精度表示常量,但编译器仍必须在 编译时 执行某些 操作 。它 确实 !
显然,“无限”精度无法解决。但是没有必要,因为源代码本身不是“无限的”(源大小是有限的)。但是, 允许 真正任意精度仍然不切实际。因此,规范为此提供了一些自由给编译器:
实现限制:尽管数字常量在语言中具有任意精度,但是编译器可能会使用内部表示形式来实现它们,而精度有限。也就是说,每个实现都必须: 用至少256位表示整数常量。 表示浮点常数,包括复数常数的部分,尾数至少为256位,带符号的指数至少为32位。 如果无法精确表示整数常数,则给出错误。 如果由于溢出而无法表示浮点数或复数常量,则给出错误。 * 如果由于精度限制而无法表示浮点数或复数常数,则四舍五入到最接近的可表示常数。这些要求既适用于文字常量,也适用于评估常量表达式的结果。
实现限制:尽管数字常量在语言中具有任意精度,但是编译器可能会使用内部表示形式来实现它们,而精度有限。也就是说,每个实现都必须:
但是,还请注意,当上述所有内容都存在时,标准包为您提供了仍然可以以“任意”精度表示和使用值(常数)的方法,请参见package go/constant。您可以查看其来源,以了解其实现方式。
go/constant
实现在中go/constant/value.go。表示此类值的类型:
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):
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)在哪里:
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)
Word
math/big/arith.go
// A Word represents a single digit of a multi-precision unsigned integer. type Word uintptr
摘要
在运行时: 预定义 类型提供有限的精度,但是您可以使用某些包(例如math/big和)“模拟”任意精度go/constant。在编译时:常量似乎提供了任意精度,但实际上,编译器可能无法做到这一点(不必这样做);但是规范仍然为所有编译器必须支持的常数提供了最低的精度,例如,整数常数必须至少以256位(32字节)表示(相比之下int64,“仅” 8字节)。
int64
创建可执行二进制文件时,必须转换常量表达式的结果(具有任意精度),并用有限精度类型的值表示-这可能是不可能的,因此可能导致编译时错误。请注意,只有 结果( 而不是中间操作数)必须转换为有限精度,常数运算可以任意精度执行。
规范未定义如何实现这种任意精度或增强精度,math/big例如,将数字的“数字”存储在切片中(其中,数字不是以10为基数表示的数字,而“数字” uintptr是类似于base的数字)。在4位元架构上显示4294967295,在64位元架构上甚至更大)。
uintptr