使用数字并对它们进行数学运算是任何编程语言的常见做法。Go 中的数值数据类型表示多种类型的数值数据,范围从普通数字到更有意义的表示,例如货币、屏幕像素大小、点、地理位置、时间、颜色代码等。因此,为了有效地处理数字,了解数字在 Go 中如何运作的基础知识非常重要。本文展示了如何使用两种最常见的数字数据类型:整数和浮点数,并快速绕道介绍了计算中的数字概念。
阅读: Go 编程入门
数字在编程中的工作原理
基数和索引是数字的两个重要属性。指数——或幂(或指数)——通常表示该数字要相乘多少次。例如:(2 x 2 x 2 = 8)和(8 = 23),因此(上标)23中的3称为索引。
我们通常不会提及或写出数字的基数,因为大多数时候,我们只处理一种称为十进制数的数字系统,因此类型很明显。但是当我们处理不止一种类型的数字系统时,我们正在谈论哪种数字系统显然不是很清楚。在计算中,常见的数字系统有四种:十进制、二进制、八进制和十六进制。同时,我们人类习惯的只有一种类型:十进制。
十进制数(或浮点数)由 0 – 9、10 个单位数字表示。其他大数字只不过是这些 0-9 数字的组合。同样,二进制有两个数字——0 和 1。八进制有八个数字,0-7。十六进制有 16 个数字。由于 10、11 等是两位数,十六进制使用字母 A 表示 10,B 表示 11,直到我们最终到达 F 表示 15。因此,前 16 个数字是从 0 到 F,即十进制表示 0-15。
现在,通常将数字的基数写为下标:(123)10 表示十进制数,(45)8 表示八进制数,(1101)2 表示二进制数,最后 (A920CD)16 表示十六进制数. 任何数字都可以从一种类型转换为另一种类型,但这超出了本节的范围。
计算中最常用的两种数值数据类型是整数和浮点数。下面我们将从数学中的数字概念快速定义它们的含义:
- 所有正数 {1, 2, 3, …} 的集合称为自然数。
- 如果我们将零添加到自然数集合 {0, 1, 2, 3, …},它们被称为整数。
- 整数是所有整数加上所有负自然数的集合 – {…, -2, -1, 0, 1, 2, 3, …}。整数的一个重要特征是它们是精确的数字并且没有四舍五入误差。
另一种类型的数字称为浮点数或浮点数,它们表示不同的精度,例如 3.14、3.147 或 -234.56 等。这也通常称为十进制数。
- 在数学上,它们是 p/q 数,其中 q 不等于 0,称为有理数。
- 与有理数相反,有无理数,小数点后有无穷无尽的不重复数字,例如 PI 或sqrt(3)或sqrt(2)的值。
- 有理数和无理数一起构成实数集。
在计算机编程中,有两种数据类型就足够了:整数表示所有正负精确数,浮点数表示所有正负不精确数或分数或实数。
计算机如何存储数据?
计算机内存使用位模式来存储数据。完全由程序员决定每个位模式在程序上下文中的含义。它可以是一个数字、一个字符或任何其他对象。这种二进制模式的解释称为数据表示或编码。因此,数字也是由固定位数表示的一段数据。
一个 n 位存储可以表示 2n 个不同的实体。例如,一个 3 位存储可以存储八种不同的二进制模式:000、001、010、011、100、101、110、111。这意味着我们表示数字 0 到 7 或字符“A”到“H”或任何其他八种不同的东西。
整数通常表示为 8 位、16 位、32 位或 64 位值。这种选择对数据类型可以表示的值的范围施加了限制。除了位长之外,表示方案(例如有符号或无符号整数)也会影响值的范围。例如,一个 8 位无符号整数的范围是 0 到 255,而一个 8 位有符号整数的范围是 -128 到 127。请注意,在这种情况下,有符号整数和无符号整数都代表 256 个不同的数字。
浮点数虽然像其他所有东西一样由位模式表示,但有点棘手。假设我们使用 32 位来表示实数和整数。一个等距的整数(如果我们放入数轴),可以存储的最大值是 232。但是,两个整数之间可以有无限个浮点数(因此精度的概念对于浮点数很重要。阅读IEE754 了解更多详情)。
精度值的微小变化,例如从 2.34567 到 2.34566,可能无法表示,因为在数轴的下端表示的值与 0 之间的间距非常小,并且随着我们在数轴的正方向或负方向。在整数和浮点数的情况下,32位存储用于保存数字,但只是浮点数中的数字不等间距,这使得它的范围在精度有限的情况下比32表示的整数范围更均匀位。然而,实数的真实性质无法在数字计算机中复制。浮点表示是我们模仿这一点的最佳方法。
了解有关在 Go 中使用字符串的所有信息。
Go 中的整数类型是什么?
Go 中最常用的两种数字数据类型是整数和浮点数。Go 表示四种不同大小的整数——8、16、32 和 64 位。去整数:
- 分别表示为int8[-27,27-1]、int16[-215, 215-1]、int32[-231, 231-1]和int64[-263, 263-1]。它们都是有符号整数。
- 有相应的无符号版本:uint8[0, 28-1,],uint16[0, 216-1],uint32[0, 232-1]和uint64[0, 264-1]。
- 如果我们不指定位大小而只写int或uint,那么它们可以分别表示 32 位或 64 位整数或无符号整数,具体取决于编译器。
- 还有另一种称为rune的类型,它是int32的别名,通常用于表示 Unicode 代码点。
- 同样,字节数据类型是uint8的别名。
- 有一种称为uintptr的无符号整数类型,其位值未指定。它用于保存指针值的位,通常用于低级编程。
- 不能存储更大数字的类型的变量会被静默截断。
有符号数以 2 的补码形式表示,其中高位保留为符号位。无符号整数使用全范围的位。
Go 中的浮点类型是什么?
有两种类型的浮点数表示为float32和float64。
- 在math包中,有一个叫做math.MaxFloat32的常量,它表示这个类型的变量可以容纳的最大float32值。
- 同样,对于 64 位大小,还有另一个名为math.float64的常量。
- float32值提供最多 6 位小数的近似精度。
- float64值提供最多 15 位小数的近似精度。
Go中的运算符和优先顺序
Go中数学、逻辑、比较相关的运算符按照优先级降序排列如下:
- 第 5 级:*(Mul)、/(Div)、%(Modulo)、<<(Left-shift)、>>(Right-shift)、&(bitwise AND)、&^(bitwise clear AND NOT)
- 级别 4:+(Add) , -(Sub) , |(bitwise OR) , ^(bitwise XOR)
- 第 3 级:==(等于)、!=(不等于)、<(小于)、<=(小于)、>(大于)、>=(大于)
- 级别 2:&&(逻辑与)
- 级别 1:||(逻辑或)
同一级别的运算符具有从左到右的关联性。因此,为了清楚起见,必须使用适当的括号。请注意,%(Modulo)运算符仅适用于整数,当与负整数(例如 -7%3)一起使用时,余数的符号与被除数的符号相同。级别(5 到 1)中的运算符具有递减的优先顺序,而同一级别的运算符具有相同的优先顺序。
但是,如果一个表达式有多个相同优先级的运算符,则计算顺序是从左到右。因此,表达式 (1+2-3+4) 的结果是 4 而不是 -4。应用熟悉的BODMAS或PEMDAS规则时要小心。由于它们,可能会有不正确的解释。当然,可以适当地使用括号来减轻混乱。
Go 中的数学运算符
Go 中的加法和减法运算符类似于数学中的运算符。让我们看几个在 Go 中使用数学运算符的快速示例:
package main
import "fmt"
func main() {
intX := 20
intY := 50
fmt.Println(intX + intY) //also, fmt.Println(intX - intY)
intY = -10 //integers can be negative, using unary operator
fmt.Println(intX + intY) //also, fmt.Println(intX - intY)
floatX := 5.67
floatY := 2.56
//To properly format the output to 2 decimal place, we can use fmt.Printf
fmt.Printf("%.2f", floatX+floatY) // fmt.Printf("%.2f", floatX+floatY)
floatY = -1.24 //floats can be negative too
fmt.Printf("\n%.2f", floatX+floatY) //fmt.Printf("%.2f", floatX+floatY)
}
如果在您的集成开发环境 (IDE) 或代码编辑器中运行,上述代码将产生以下输出:
70 10 8.23 4.43
现在,如果我们尝试使用不匹配的数据类型进行如下计算会发生什么:
fmt.Printf("\n%.2f", intX+floatY) //error fmt.Printf("\n%d", intX+floatY) //error
这在 Go 中是不允许的。但是,我们可以将变量转换为相应的类型并进行计算而不会出现任何错误:
fmt.Printf("\n%.2f", float64(intX)+floatY) //转入浮点数 fmt.Printf("\n%d", intX+int(floatY)) //将float转换为int
但请注意,将变量类型转换为另一种类型可能会导致信息丢失。例如,将浮点数转换为int变量会将其四舍五入为最接近的整数。小数值被截断。因此,在将一个变量类型转换为另一种类型时要小心。
Go中的乘法和除法运算符
Go 中的乘法和除法运算符也类似于它们的数学等价物。这是一个简单的例子:
package main
import "fmt"
func main() {
intX := 56
intY := 12
fmt.Println(intX * intY)
fmt.Println(intX / intY)
floatX := 5.67
floatY := 2.56
fmt.Printf("%.2f", floatX*floatY)
fmt.Printf("\n%.2f", floatX/floatY)
}
在您的 IDE 或代码编辑器中运行此代码会导致:
672 4 14.52 2.21
请注意,Go 中的除法运算符 / 在整数的情况下执行下限除法,这意味着除法结果(商)的小数部分会四舍五入到最接近的下限(整数)值。除法的结果通常有两个整数部分:商和余数。除法/ , 运算符返回商,而模 % , 运算符返回除法后的余数。查看以下示例:
package main
import "fmt"
func main() {
intX := 45
intY := 7
fmt.Printf("%d / %d = %d (Quotient)\n", intX, intY, (intX / intY))
fmt.Printf("%d %% %d = %d (Remainder)\n", intX, intY, (intX % intY))
}
输出:
45 / 7 = 6(商) 45 % 7 = 3(余数)
Go 中的 Mod 函数
我们可以用浮点数据类型做模吗?答案是肯定的,但不是使用模数 %运算符。我们必须使用数学包中的Mod函数。这是一个简单的例子:
package main
import (
"fmt"
"math"
)
func main() {
floatX := 57.0
floatY := 7.0
fmt.Printf("%.2f Mod %.2f = %.2f\n", floatX, floatY, math.Mod(floatX, floatY))
}
输出将是:
57.00 Mod 7.00 = 1.00
除此之外,还有赋值运算符= 和写成x+=5的速记运算符,这实质上意味着x=x+5。速记运算符可以应用于所有 5 级和 4 级运算符,例如 x*=5(means, x=x*5), x&^=3(means, x=x&^3), x^=6(means , x=x^6) 等。
了解 Go 中的其他方法。
Go 中的位运算符
位运算符用于位计算。以下是如何使用它们的快速示例:
package main
import (
"fmt"
)
func main() {
var x uint8 = 10<<2 | 9<<3
var y uint8 = 16<<3 | 8<<2
fmt.Printf("x = %08b\n", x)
fmt.Printf("y = %08b\n", y)
fmt.Printf("x&y = %08b\n", x&y) //AND
fmt.Printf("x|y = %08b\n", x|y) //OR
fmt.Printf("x^y = %08b\n", x^y) //XOR
fmt.Printf("x&^y = %08b\n", x&^y) //AND NOT
fmt.Printf("x<<2 = %08b\n", x<<2) //Left-shift fmt.Printf("x>>1 = %08b\n", x>>1) //Right-shift
}
输出:
x = 01101000 y = 10100000 x&y = 00100000 x|y = 11101000 x^y = 11001000 x&^y = 01001000 x<<2 = 10100000 x>>1 = 00110100
在 Python 中处理大数
在某些情况下,我们可能需要对超出int64/uint64 float64类型限制的数字进行数学运算。Go 标准库为整数提供了三种无限精度的数字类型,称为big.Int用于有符号整数,big.Rat用于有理数,big.Float用于浮点数。
big.Rat (有理数)只能存储有理数。big.Rat中不能存储无理数,例如PI、e或sqrt(3)的值等。大数可以是任意长度(仅受内存限制),并且它们的计算比内置的原始类型慢很多。对于数学计算,我们不能使用通常的运算符。相反,我们必须使用诸如Add()、Mul()等方法。这是一个快速且非常简短的示例,展示了如何在 Go中使用大数字:
package main
import (
"fmt"
"math/big"
)
func main() {
bigInt1 := big.NewInt(int64(20)) //NewInt(x) returns an *Int set to the value of the int64 argument x
bigInt2 := big.NewInt(int64(30))
bigFloat1 := big.NewFloat(8.3983)
//NewFloat(x) returns a *Float initialized to the float64 argument x
bigFloat2 := big.NewFloat(7.98327643)
bigRat1 := big.NewRat(22, 7)
// NewRat(x, y) returns a *Rat set to the fraction
// x/y where x and y are int64 values
bigRat2 := big.NewRat(67, 20)
fmt.Println(bigInt1.Add(bigInt1, bigInt2))
fmt.Println(bigFloat1.Mul(bigFloat1, bigFloat2))
fmt.Println(bigRat1.Sub(bigRat1, bigRat2))
}
如果您在代码编辑器中键入并运行此代码,其输出将是:
50 67.04595044206901 -29/140
关于 Go 中数学运算符的最后思考
在这里,我们快速浏览了数学运算符和数字数据类型在 Go 中的应用,仅关注整数和浮点类型。Go 在标准库中提供了更多内容来处理数字及其使用的数学部分。我们通常不处理超过 64 位空间的数字。整数和浮点数足以满足我们在 Go 中的大部分需求,除非我们正在执行一些科学计算。
科学计算有时需要计算中必须精确的大数字。Go 在标准库中也有相关规定。大数字的使用可能很少见,但它们有其用途。然而,在大多数情况下,应该将原始数据类型作为所有计算的基础,因为它们要快得多。另外,请记住,为了提高效率,整数总是比浮点数好。