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