Go如何对常量执行算术运算? [英] How does Go perform arithmetic on constants?

查看:124
本文介绍了Go如何对常量执行算术运算?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在阅读关于Go中的常量的帖子,我正在试着了解它们被存储和使用在内存中。您可以在Go中对非常大的常量执行操作,只要结果放在内存中,就可以将该结果强制转换为类型。例如,这段代码打印出 10 ,如您所期望的那样:

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

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

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



无类型的任意精度常量不在运行时存在,常量只在编译时生效(编译期间) 。这就是说,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

  //一个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字节)相比。

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

任意或更高精度的实现不是由规范定义的,例如 math / big 例如存储切片中数字的数字(其中数字不是base 10表示,但digit是一个 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:

Spec: Constants:

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆