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

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

问题描述

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

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)

这到底是如何运作的?在某些时候,Go 必须将 1e10001e999 存储在内存中,以便对它们执行操作.那么常量是如何存储的,Go 又是如何对它们进行算术运算的?

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?

推荐答案

简短摘要 (TL;DR) 在答案的末尾.

无类型的任意精度常量在运行时不存在,常量只在编译时(在编译期间)存在.话虽如此,Go 不必在运行时以任意精度表示常量,仅在编译应用程序时才如此.

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)

源代码中有一个常量Huge(并将在包对象中),但它不会出现在您的可执行文件中.相反,对 fmt.Println() 的函数调用将记录一个传递给它的值,其类型为 float64.因此,在可执行文件中,只会记录 float64 值为 10.0 的值.可执行文件中没有任何数字是 1e1000 的迹象.

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.

这个 float64 类型是从 untyped 常量 Hugedefault 类型派生而来的.1e1000 是一个 浮点文字.验证一下:

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

回到任意精度:

规范:常量:

数字常量代表任意精度的精确值,不会溢出.

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:

  • 表示至少 256 位的整数常量.
  • 表示浮点常量,包括复数常量的部分,尾数至少为 256 位,带符号指数至少为 32 位.
  • 如果无法精确表示整数常量,则报错.
  • 如果由于溢出而无法表示浮点数或复数常量,则报错.
  • 如果由于精度限制而无法表示浮点或复数常量,则舍入到最接近的可表示常量.这些要求既适用于文字常量,也适用于计算常量表达式的结果.

但是,还要注意,当上述所有内容都说时,标准包为您提供了仍然以任意"精度表示和处理值(常量)的方法,请参阅包 go/constant.您可以查看它的来源以了解它是如何实现的.

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.

实现在 go/constant/value.go.表示这些值的类型:

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 }
)

如您所见,math/big 包用于表示无类型的任意精度值.big.Int 是例如(来自 math/big/int.go):

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
}

nat 在哪里(来自 math/big/nat.go):

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

最后 Word 是(来自 math/big/arith.go)

// A Word represents a single digit of a multi-precision unsigned integer.
type Word uintptr

<小时>

总结

在运行时:预定义类型提供有限的精度,但您可以模仿"某些包的任意精度,例如 math/biggo/constant.在编译时:常量看似提供任意精度,但实际上编译器可能无法做到这一点(不必);但该规范仍然为所有编译器必须支持的常量提供最小精度,例如整数常量必须用至少 256 位表示,即 32 个字节(与仅"8 个字节的 int64 相比).

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 be 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.

规范没有定义如何实现这种任意或增强的精度,例如 math/big 将数字的数字"存储在切片中(其中数字不是基数的数字)10 表示,但数字"是 uintptr,它类似于 32 位架构上的基本 4294967295 表示,在 64 位架构上甚至更大).

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天全站免登陆