GO语言基础篇(十)- 整数和浮点数详解

这是我参与8月更文挑战的第 10 天,活动详情查看: 8月更文挑战

基础数据类型(一)

毫无疑问,计算机底层全是位,而实际操作则是基于大小固定的单元中的数值,称为 ( word ) ,这些值可解释为整数、浮点数、位集( bitset )或内存地址等,进而构成更大的聚合体,以表示数据包、像素、文件以及其他种种。Go的数据类型宽泛,并有多种组织方式,向下匹配硬件特性,向上满足程序员所需,从而可以方便地表示复杂数据结构

Go 的数据类型分四大类:基础类型(basic type)、聚合类型(aggregate type)、引用类型(reference type)和接口类型(interface type)。基础类型包括:数字(number)、字符串(string)和布尔类型(boolen)。聚合类型包括:数组( array )和结构体(struct)—-是通过组合各种简单类型得到的更复杂的数据类型。引用是一个大的分类,其中包括多种不同的类型,如指针(pointer)、slice、map、函数(function)以及通道(channel)

整数

Go同时具备有符号整数无符号整数。有符号整数分四种大小:8位、16位、32位、64位,用int8、int16、int32、int64表示,对应的无符号整数是:uint8、uint16、 unint332、uint64

此外还有两种类型intuint。在特定平台上,其大小与原生的有符号整数\无符号整数相同,或等于该平台上的运算效率最高的值。int是目前使用最广泛的数值类型。这两种类型大小相等,都是32位或64位,但不能认为它们一定就是32位,或一定就是64位;即使在同样的硬件平台上,不同的编译器可能选用不同的大小

rune类型是int32类型的同义词,常常用于指明一个值是 Unicode码点( code point)(如果你对Unicode的码点不熟悉,强烈建议看这篇文章 ),这两个名称可互换使用。同样,byte类型是uint8类型的同义词,强调一个值是原始数据,而非量值

最后,还有一种无符号整数 uintptr,其大小并不明确,但足以完整存放指针。 uintptr类型仅仅用于底层编程,例如在Go程序与C程序库或操作系统的接口界面

int、uint和 uintptr都有别于其大小明确的相似类型的类型。就是说,int和int32是不同类型,尽管int天然的大小就是32位,并且int值若要当作int32使用,必须显式转换;反之亦然

有符号整数以补码表示,保留最高位作为符号位,n位数字的取值范围是-2^(n-1) ~ 2^(n-1)-1。无符号整数由全部位构成其非负值,范围是0 ~ 2^n – 1。例如,int8可以从-128到127取值,而unit8从0到255取值

二元运算符

Go的二元操作符涵盖了算术、逻辑和比较等运算。按优先级的降序排列如下:

*    /    %    <<    >>    &    &^
+    -    |    ^
==    !=    <    <=    >    >=
&&    
||  
同级别的运算符遵循做结合律
复制代码

算术运算符+、-、*、/可应用于整数、浮点数和复数,而取模运算符%仅能用于整数。取模运算符%的行为因编程语言而异。就Go而言,取模余数的正负号总是与被除数一致,所以-5%3和-5%-3都得-2。除法运算行为取决于操作数是否都为整型,整数相除,商会舍弃小数部分,所以5.0/4.0得到1.25,而5/4结果是1

不论是有符号数还是无符号数,若表示算术运算结果所需的位超出该类型的范围,就称为溢出溢出的高位部分会无提示地丢弃。假如原本的计算结果是有符号类型,且最左侧位是1,则会形成负值,以int8为例:

var u uint8 =255
fmt. Println(u, u+1, u*u) // 255   0   1 

var i int8 =127
fmt. Println(i, i+1, i*i) //127   -128   1
复制代码

位运算符

Go也具备下列位运算符,前四个对操作数的运算逐位独立进行,不涉及算数进位或正负号

&        位运算 AND
|        位运算 OR
^        位运算 XOR
&^       位运算(AND NOT)
<<       左移
>>       右移
复制代码

如果作为二元运算符,运算符^表示按位“异或” ( XOR ) ;若作为一元前缀运算符,则它表示按位取反或按位取补,运算结果就是操作数逐位取反。运算符&^是按位清除( AND NOT ) :表达式 z = x &中,若 y 的某位是 1 ,则 Z 的对应位等于 0

	var x uint8 = 1 << 1 | 1 << 5
	var y uint8 = 1 << 1 | 1 << 2

	fmt.Printf("%08b\n", x) //  00100010  集合{1, 5}
	fmt.Printf("%08b\n", y) //  00000110  集合{1, 2}

	fmt.Printf("%08b\n", x&y) // 00000010  交集{1}
	fmt.Printf("%08b\n", x|y) // 00100110  并集{1, 2, 5}
	fmt.Printf("%08b\n", x^y) // 00100100  对称差{2, 5}
	fmt.Printf("%08b\n", x&^y) // 00100000 差集{5}

	for i := uint8(0); i < 8; i++ {
		if x&(1<<i) != 0 {
			fmt.Println(i) //"1","5"
		}
	}

	fmt.Printf("%08b\n", x<<1) // 01000100  集合{2, 6}
	fmt.Printf("%08b\n", x>>1) // 00010001  集合{0, 4}
复制代码

在移位运算 x << n 和 x >> n 中,操作数 n 决定位移量,而且 n 必须为无符号型;操作数 x 可以是有符号型也可以是无符号型。算术上,左移运算 x << n 等价于 x 乘以 2^n ;而右移运算 x >> n 等价于 x 除以 2^n ,向下取整。左移以 0 填补右边空位,无符号整数右移同样以 0 填补左边空位,但有符号数的右移操作是按符号位的值填补空位。因此,请注意,如果将整数以位模式处理,须使用无符号整型

通常将某一种数据类型转换成另一种,需要显式转换。对于算术和逻辑的二元运算符,其操作的类型必须相同

var apples int32 = 1
var oranges int16 = 2
var compote = apple + oranges//编译错误,类型不匹配
复制代码

浮点数

Go 具有两种大小的浮点数 float32float64 。其算术特性遵从 IEEE 754 标准。这两个类型的值可从极细微到超宏大。math 包给出了浮点值的极限。常量 math . MaxFloat32 是 float32 的最大值,大约为 3 . 4e38,而 math . MaxFloat64则大约为 1.8e308。相应地,最小的正浮点值大约为1.4e-45和4.9e-324

十进制下,float32 的有效数字大约是 6 位,float64 的有效数字大约是 15 位。绝大多数情况下,应优先选用似float64,因为除非格外小心,否则,float32的运算会迅速累积误差。并且,float32能精确表示的正整数范围有限:

var f float32 = 16777216 // 1 << 24
fmt.Println(f == f+1) // true
复制代码

在源码中,浮点数可写成小数

const e = 2.71828//近似值
复制代码

非常小或非常大的数字最好使用科学记数法表示,此方法在数量级指数前写字母e或E

const Avogadro = 6.02214129e23
const Planck = 6.62606957e-34
复制代码

浮点值能够方便的通过Printf的谓词%g输出,该谓词会自动保持足够的精度(格式化输出看这里),并选择最简洁的表示方式。但是对于数据表,%e(有指数)或%f(无指数)的形式更合适,这三个谓词都能掌控输出宽度和数值精度

for x:=0;x < 8; x++ {
     fmt.Printf("x = %d e^x = %8.3f\n", x, math.Exp(float64(x)))
}

输出:
x = 0 e^x =    1.000
x = 1 e^x =    2.718
x = 2 e^x =    7.389
x = 3 e^x =   20.086
x = 4 e^x =   54.598
x = 5 e^x =  148.413
x = 6 e^x =  403.429
x = 7 e^x = 1096.633
复制代码

除了大量常见的数学函数之外,math包还有函数用于创建和判断IEEE 754标准定义的 特殊值:正无穷大和负无穷大,它表示超出最大许可值的数及除以零的商;以及NaN(Not a Number),它表示数学上无意义的运算结果(如0/0或Sqrt(-i))

var z float64
fmt.Println(z,-z, 1/z, -1/z, z/z) // 0 -0 +Inf -Inf NaN
复制代码

math.IsNaN函数判断其参数是否是非数值math.NaN函数则返回非数值(NaN) 。在数字运算中,我们倾向于将NaN当作信号值(sentinel value),但直接判断具体的计算结果是否 为NaN可能导致潜在错误,因为与NaN的比较总不成立(除了 !=,它总是与==相反):

nan := math.NaN()
fmt.Println(nan == nan, nan < nan, nan > nan) // false false false
复制代码

复数

复数应该是在高中数学中学过,这里可以简单看一下在数学中,复数是一个什么样的数。首先我们知道,通常把它x轴上的任意一个点都当做一个实数(整数或浮点数),而复数就不仅仅是x轴上的数,而是在二维平面里的一个数

之所能够做到,很关键的一点就是规定了

i = √-1(根号-1)
复制代码

根号-1我们知道是不存在的,它是不存在这样的实数,然后我们想象的一个数叫i,它等于根号-1。比如3 + 4i,3就是这个复数的实部,4是这个复数的虚部。那3 + 4i是怎样的一个数?

image.png

其实就像这样,沿实数的数轴走3,然后往上走4(那3-4i就是往下走4)。这里的黄色斜线的长度是5,是怎么计算出来的?

|3+4i| = √3^2 + √4^2 = 5
复制代码

用绝对值符号,一般叫做模,3+4i的模就等于3的平方加4的平方开根号,得到的就是5。i还有其它的一些特性,如下

i^2 = -1(就是把根号-1带进去算)
i^3 = -1
i^4 = 1
......
其实每次多乘一个i,就是往逆时针转90°
复制代码

Go具备两种大小的复数complex64complexl28,二者分别由float32和float64构成。 内置的complex函数根据给定的实部和虚部创建复数,而内置的real函数和imag函数则分别提取复数的实部和虚部

var x complexl28 = complex(1, 2) // 1+2i
var y complexl28 = complex(3, 4) // 3+4i
fmt.Println(x*y) // -5+10i
fmt.PrintIn(real(x*y)) // -5
fmt.PrintIn(imag(x*y)) // 10
复制代码

源码中,如果在浮点数或十进制整数后面紧接着写字母i,如3.141592i或2i,它就变 成一个虚数,表示一个实部为0的复数

fmt.Println(1i * 1i) // "(-1+0i)", i² = -1
复制代码

根据常量运算规则,复数常量可以和其他常量相加(整型或浮点型,实数和虚数皆可),这让我们可以自然地写出复数,如1+2i,或等价地,2i+1。前面x和y的声明可以简写为:

x := 1 + 2i 
y := 3 + 4i
复制代码

可以用==或!=判断复数是否等值若两个复数的实部和虚部都相等,则它们相等。 math/cmplx包提供了复数运算所需的库函数,例如复数的平方根函数和复数的幕函数

fmt.Printin(cmplx.Sqrt(-1)) // "(0+1i)
复制代码

布尔值

bool型的值或布尔值(boolean)只有两种可能:真(true)和假(false) 。if和for语句 里的条件就是布尔值,比较操作符(如==和<)也能得出布尔值结果。一元操作符(!)表示逻辑取反,因此!true就是false,或者可以说(!true==false)==true。比如,考虑到代码风格,布尔表达式x==true相对冗长,我们总是简化为x

布尔值可以由运算符&&(AND)以及||(0R)组合运算,这可能引起短路行为:如果运算符左边的操作数已经能直接确定总体结果,则右边的操作数不会计算在内,所下面的表达式是安全的

s != "" && s[0] == 'x'
其中,如果作用于空字符串,s[0]会触发宕机异常
复制代码

因为&&较II优先级更高(助记窍门:&&表示逻辑乘法,II表示逻辑加法),所以如下 形式的条件无须加圆括号

if 'a' < c && c < 'z' ||
    'A' < c && c < 'Z' ||
    '0' < c && c < '9' {
    //ASCII字母或数字
}
复制代码

布尔值无法隐式转换成数值(如0或1 ),反之也不行。如下状况下就有必要使用显式if

i := 0
if b {
    i = 1
}
假如转换操作常常用到,就值得专门为此写个函数
func btoi(b bool) int {
    if b {
        return 1
    }

    return 0
}
复制代码

参考

《Go程序设计语言》—-艾伦 A. A. 多诺万

《Go语言学习笔记》—-雨痕

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享