理解 Go 中的数学运算符

使用数字并对它们进行数学运算是任何编程语言的常见做法。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]
  • 如果我们不指定位大小而只写intuint,那么它们可以分别表示 32 位或 64 位整数或无符号整数,具体取决于编译器。
  • 还有另一种称为rune的类型,它是int32的别名,通常用于表示 Unicode 代码点。
  • 同样,字节数据类型是uint8的别名。
  • 有一种称为uintptr的无符号整数类型,其位值未指定。它用于保存指针值的位,通常用于低级编程。
  • 不能存储更大数字的类型的变量会被静默截断。

有符号数以 2 的补码形式表示,其中高位保留为符号位。无符号整数使用全范围的位。

Go 中的浮点类型是什么?

有两种类型的浮点数表示为float32float64

  • 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。应用熟悉的BODMASPEMDAS规则时要小心。由于它们,可能会有不正确的解释。当然,可以适当地使用括号来减轻混乱。

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中不能存储无理数,例如PIesqrt(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 在标准库中也有相关规定。大数字的使用可能很少见,但它们有其用途。然而,在大多数情况下,应该将原始数据类型作为所有计算的基础,因为它们要快得多。另外,请记住,为了提高效率,整数总是比浮点数好。