Go如何对常量执行算术运算? [英] How does Go perform arithmetic on constants?
问题描述
我一直在阅读关于Go中的常量的帖子,我正在试着了解它们被存储和使用在内存中。您可以在Go中对非常大的常量执行操作,只要结果放在内存中,就可以将该结果强制转换为类型。例如,这段代码打印出 10
,如您所期望的那样:
const Huge = 1e1000
fmt.Println(Huge / 1e999)
这是如何工作的引擎盖?在某些时候,Go必须在内存中存储 1e1000
和 1e999
,以便对它们执行操作。那么如何存储常量,以及Go如何对它们进行算术运算?
无类型的任意精度常量不在运行时存在,常量只在编译时生效(编译期间) 。这就是说,Go不必在运行时以任意精度表示常量,只能在编译应用程序时使用。
为什么?因为常量不会被编译到可执行文件中。他们不一定非要。让我们来看看你的例子:
$ p $ const Huge = 1e1000
fmt.Println(Huge / 1e999)
在源代码中有一个常量 Huge
/ em>(并且将位于包对象中),但它不会出现在您的可执行文件中。相反,调用 fmt.Println()
的函数将被记录一个传递给它的值,它的类型将是 float64
。因此在可执行文件中只会记录 float64
值 10.0
。可执行文件中没有任何编号 1e1000
的符号。
$ b
float64
type来源于无类型常量巨大
的默认类型。 1e1000
是一个浮点文字。验证它: const Huge = 1e1000
x:= Huge / 1e999
fmt.Printf( %T,x)//打印float64
返回任意精度:
数字常数表示任意精度的精确值并且不会溢出。
所以常量表示任意精度的精确值。正如我们所看到的,不需要在运行时以任意精度表示常量
,但编译器仍然必须在编译时间处执行某些操作。它确实!显然无限精度无法处理。但是没有必要,因为源代码本身不是无限的(源的大小是有限的)。尽管如此,允许真正的任意精度是不现实的。所以规范为编译器提供了一些自由:
$ b
实现限制:尽管数字常量在语言中具有任意精度,但编译器可能会使用精度有限的内部表示来实现它们。也就是说,每个实现必须:
- 表示至少有256位的整数常量。
- 表示浮点常量,包括复常数的部分,尾数至少为256位,有符号指数至少为32位。
- 如果无法表示整数常量。
- 如果由于溢出而无法表示浮点或复数常量,则给出错误。
- 舍入到最近的可表示常量如果由于精度限制无法表示浮点或复数常量。
这些要求既适用于字面常量,也适用于评估常量表达式的结果。
不过,还要注意的是,当上述所有内容都提供时,用任意精度表示和使用值(常量),请参阅包 go /恒定
。你可以看看它的来源,以了解它是如何实现的。
实现在 go / constant / value.go
。表示这些值的类型:
//值表示Go常量的值。
类型值接口{
// Kind返回值类型。
Kind()Kind
// String返回一个简短的,人类可读的值。
//对于数值,结果可能是近似值;
//对于字符串值,结果可能是缩短的字符串。
//使用ExactString来表示一个完全代表值的字符串。
String()字符串
// ExactString返回值的精确可打印形式。
ExactString()字符串
//防止外部实现。
implementsValue()
}
类型(
unknownVal结构{}
boolVal bool
stringVal字符串
int64Val int64 // Int值可表示为int64
intVal结构体{val * big.Int} // Int值不能表示为int64
ratVal结构体{val * big.Rat} //浮点值可表示为分数
floatVal struct {val * big.Float} //浮点值不能用分数表示
complexVal struct {re,im Value}
)
正如您所见, math / big 软件包用于表示无类型的任意精度值。 big.Int
适用于例子(来自 math / big / int.go $ c $
//一个Int代表一个有符号的多精度整数。
// Int的零值表示值0.
类型Int结构{
neg bool //符号
abs nat //整数的绝对值
其中 nat
a href =https://golang.org/src/math/big/nat.go =nofollow> math / big / nat.go
):
//一个无符号整数x,形式为
//
// x = x [n-1] * _B ^(n-1)+ x [n-2] * _B ^(n-2)+ ... + x [1] * _B + x [0]
//
// 0< = x [i]< _B和0 <= i < n被存储在长度为n的片段中,
//以数字x [i]作为片段元素。
//
//如果切片不包含前导0位,则数字将被标准化。
//在算术运算期间,可能会发生非规格化的值,但是在返回最终结果之前总是将
//归一化。标准化的
//表示0是空或零切片(长度= 0)。
//
type nat [] Word
最后 Word
是(来自 math / big / arith.go
)
//一个Word代表一个多精度无符号整数的单个数字。
type word uintptr
总结 在运行时:预定义的类型提供了有限的精度,但是您可以使用某些包模仿任意精度,例如 math / big
和 go / constant
。在编译时:常量似乎提供了任意精度,但实际上编译器可能不会达到此目的(不必);但该规范仍然为所有编译器必须支持的常量提供最小精度,例如整数常量必须至少用256位表示,这是32字节(与 int64
,它是只有8字节)相比。
创建可执行二进制文件时,常量表达式的结果(具有任意精度)必须转换并用有限精度类型的值表示 - 这可能是不可能的,因此可能导致编译时错误。请注意,只有结果 - 不是中间操作数 - 必须转换为有限精度,常量运算以任意精度执行。
任意或更高精度的实现不是由规范定义的,例如 uintptr
,这与32位体系结构中的基本4294967295表示形式相似,而在64位体系结构中则更大)。
I've been reading this post on constants in Go, and I'm trying to understand how they are stored and used in memory. You can perform operations on very large constants in Go, and as long as the result fits in memory, you can coerce that result to a type. For example, this code prints 10
, as you would expect:
const Huge = 1e1000
fmt.Println(Huge / 1e999)
How does this work under the hood? At some point, Go has to store 1e1000
and 1e999
in memory, in order to perform operations on them. So how are constants stored, and how does Go perform arithmetic on them?
Short summary (TL;DR) is at the end of the answer.
Untyped arbitrary-precision constants don't live at runtime, constants live only at compile time (during the compilation). That being said, Go does not have to represent constants with arbitrary precision at runtime, only when compiling your application.
Why? Because constants do not get compiled into the executable binaries. They don't have to be. Let's take your example:
const Huge = 1e1000
fmt.Println(Huge / 1e999)
There is a constant Huge
in the source code (and will be in the package object), but it won't appear in your executable. Instead a function call to fmt.Println()
will be recorded with a value passed to it, whose type will be float64
. So in the executable only a float64
value being 10.0
will be recorded. There is no sign of any number being 1e1000
in the executable.
This float64
type is derived from the default type of the untyped constant Huge
. 1e1000
is a floating-point literal. To verify it:
const Huge = 1e1000
x := Huge / 1e999
fmt.Printf("%T", x) // Prints float64
Back to the arbitrary precision:
Numeric constants represent exact values of arbitrary precision and do not overflow.
So constants represent exact values of arbitrary precision. As we saw, there is no need to represent constants with arbitrary precision at runtime, but the compiler still has to do something at compile time. And it does!
Obviously "infinite" precision cannot be dealt with. But there is no need, as the source code itself is not "infinite" (size of the source is finite). Still, it's not practical to allow truly arbitrary precision. So the spec gives some freedom to compilers regarding to this:
Implementation restriction: Although numeric constants have arbitrary precision in the language, a compiler may implement them using an internal representation with limited precision. That said, every implementation must:
- Represent integer constants with at least 256 bits.
- Represent floating-point constants, including the parts of a complex constant, with a mantissa of at least 256 bits and a signed exponent of at least 32 bits.
- Give an error if unable to represent an integer constant precisely.
- Give an error if unable to represent a floating-point or complex constant due to overflow.
- Round to the nearest representable constant if unable to represent a floating-point or complex constant due to limits on precision. These requirements apply both to literal constants and to the result of evaluating constant expressions.
However, also note that when all the above said, the standard package provides you the means to still represent and work with values (constants) with "arbitrary" precision, see package go/constant
. You may look into its source to get an idea how it's implemented.
Implementation is in go/constant/value.go
. Types representing such values:
// 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 }
)
As you can see, the math/big
package is used to represent untyped arbitrary precision values. big.Int
is for example (from 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
}
Where nat
is (from 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
And finally Word
is (from math/big/arith.go
)
// A Word represents a single digit of a multi-precision unsigned integer.
type Word uintptr
Summary
At runtime: predefined types provide limited precision, but you can "mimic" arbitrary precision with certain packages, such as math/big
and go/constant
. At compile time: constants seemingly provide arbitrary precision, but in reality a compiler may not live up to this (doesn't have to); but still the spec provides minimal precision for constants that all compiler must support, e.g. integer constants must be represented with at least 256 bits which is 32 bytes (compared to int64
which is "only" 8 bytes).
When an executable binary is created, results of constant expressions (with arbitrary precision) have to converted and represented with values of finite precision types – which may not be possible and thus may result in compile-time errors. Note that only results –not intermediate operands– have to be converted to finite precision, constant operations are carried out with arbitrary precision.
How this arbitrary or enhanced precision is implemented is not defined by the spec, math/big
for example stores "digits" of the number in a slice (where digits is not a digit of the base 10 representation, but "digit" is an uintptr
which is like base 4294967295 representation on 32-bit architectures, and even bigger on 64-bit architectures).
这篇关于Go如何对常量执行算术运算?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!