天天看點

025-整數和浮點數

本文程式路徑是 ​

​gopl/basictypes/number​

​.

1. 整數類型和浮點數類型

1.1 整數類型

go 語言和 c 語言非常像,整數也分為有符号和無符号。在 go 語言裡,整數在記憶體占用的寬度有 8bit, 16bit, 32bit, 64bit 這四種。有符号是 ​

​int8, int16, int32, int64​

​​,而無符号是 ​

​uint8, uint16, uint32, uint64​

​.

除了以上 8 種整數類型外,還有:

  • ​rune​

    ​ 類型,32 bit 整數,主要用來表示 unicode 碼點。
  • ​byte​

    ​ 類型,8 bit 整數,用來表示原始位元組資料。
  • ​int​

    ​​ 和​

    ​uint​

    ​類型,寬度和平台及編譯器相關,32 bit 或 64 bit.
  • ​uintptr​

    ​,寬度和平台及編譯器相關,用于存放指針。它保證放得下一個指針。

在 go 裡使用 ​

​Printf​

​​ 函數列印整數使用 ​

​%d​

​​ 來格式化輸出,無論它是有符号還是無符号都适用。這一點和 c 語言有一點差別,c 語言使用 ​

​u%​

​ 格式化輸出無符号整數。

關于整數類型的使用規則,和 c 語言并沒有多大差別,概念都是相通的。無論是 c 還是 go,關于有符号、無符号、溢出、進位我需要提兩嘴,因為很多同學這幾個概念不清楚。

1.2 浮點數類型

go 裡的浮點數類型和 c 語言一樣,有 32 bit 的 ​

​float32​

​​(對應 c 語言的 ​

​float​

​​ 類型) 和 64bit 的 ​

​float64​

​​(對應 c 語言的 ​

​double​

​ 類型)。

關于浮點數,這裡就隻講這麼多了。因為你完全可以和 c 語言類比。

2. 符号

以 8 bit 數字為例講解。

2.1 映射規則

在記憶體裡用 8bit 表示整數,一共可以表示 256 個數。也就是從 ​

​00000000​

​​ ~ ​

​11111111​

​. 在程式裡,需要将這 256 個數和數學中的 256 個數一一對應起來。

對應規則是怎樣的呢?你可以這樣(方案一):

00000000 -> 0
00000001 -> 1
...
01111111 -> 127
...
10000000 -> 128
11111111 -> 255      

也可以這樣(方案二):

00000000 -> 0
00000001 -> 1
...
01111111 -> 127
...
10000000 -> -128
10000001 -> -127
...
11111111 -> -1      

甚至可以這樣(方案三):

00000000 -> -128
00000001 -> -127
...
01111111 -> -1
10000000 -> 0
10000001 -> 1
...
11111111 -> 127      

然而計算機科學發展了這麼多年,實際上隻有方案一和方案二被實際采用,而方案三棄之,因為它不太好用。

實際上方案一就是我們人類所說的無符号數映射規則,而方案二則是有符号數的映射規則 。

2.2 關于計算

下面是重點:

cpu 執行加減法計算的時候,并不在乎參與運算的數字是有符号還是無符号。

什麼意思呢?cpu 在執行 1000000 + 01000000 (二進制) 的時候,并不關心這個二進制數字映射的實際數字是什麼,換句話說,cpu 不關注數字是否是有符号還是無符号的。

對于 cpu 來說,1000000 + 01000000 執行結果就是 11000000.

接下來我們以『人類視角』看待上面的運算,如果上面的數字是無符号的,相當于:

128+64=196

128

+

64

=

196

如果上面的數字是有符号的,相當于:

−128+64=−64

128

+

64

=

64

沒錯,11000000 在無符号下是 196,在有符号下就是 -64.

既然如此,那符号有什麼作用?記住下面一點:

符号在程式裡:

  • 在比較大小的時候有用。
  • 輸出到螢幕的時候有用。

舉例:

對于有符号來說,10000000 < 01000000,對無符号來說,10000000 > 01000000.

對于無符号來說,10000000 輸出到螢幕是 128,對于無符号來說,它輸出到螢幕是 -128.

下面是一個運作執行個體:

​a​

​​ 是以有符号的視角去看待二進制數,而 ​

​ua​

​​ 是以無符号視角看待。​

​c​

​​ 是兩個數字的和,顯示為 -5 是因為我們以有符号去看待 ​

​c​

​​,如果以無符号視角看待 ​

​c​

​,那就是 251.

// demo01.go
package main

import "fmt"

func main() {
    var a int8 = -10
    var b int8 = 5
    var ua uint8 = uint8(a)
    var ub uint8 = uint8(b)
    fmt.Printf("a = %d, b = %d\n", a, b)
    fmt.Printf("ua = %d, ub = %d\n", ua, ub)

    // 1. 運算和符号無關. Print 輸出數字,需要顯式告訴它是否有無符号。
    c := a + b
    fmt.Printf("c = a + b = %d\n", c)
    fmt.Printf("uint8(c) = %d\n", uint8(c))

    uc := ua + ub
    fmt.Printf("uc = ua + ub = %d\n", uc)
    fmt.Printf("int8(uc) = %d\n", int8(uc))

    // 2. 比較大小和符号有關
    var x bool = a < b
    fmt.Printf("a < b = %v\n", x)
    fmt.Printf("ua < ub = %v\n", ua < ub)
}      
025-整數和浮點數

圖1 數字運算

另外,還舉了個例子,符号會影響大小比較。正如 a < b 為真,ua < ub 為假。

3. 進位和溢出

  • 進位是對 cpu 說的,溢出是對人說的。
  • 進位不在乎兩個數是否有符号,溢出在乎是否是有符号。

3.1 進位

兩個二進進制數相加,如果最高位進位超過了數字寬度,則産生進位。

例如 8 bit 寬度的 2 進制數 11111111 + 00000001 = (1)00000000,括号裡産生的進位被丢棄。

3.2 溢出

以有符号視角看待兩個數,如果運算結果超出表示範圍,則溢出。以 8bit 整數為例,如果是有符号,則範圍是 -128 ~ 127. 如果是無符号,則範圍是 0 ~ 255.

繼續用 3.1 中的例子。如果用有符号視角看待上面的計算,相當于 -1 + 1 = 0,結果在 -128 ~ 127 之間,沒有溢出。

如果以無符号視角看待,相當于 255 + 1 = 256,結果超出了 0 ~ 255,溢出。

上面的例子告訴我們,兩個數想加是否溢出,取決于你以什麼視角看待它,它是受人類主觀影響的。而是否進位,則取決于 cpu 自己,不受人類左右。

4. 輸出格式控制

4.1 整數輸出

下面是整數所使用的格式控制。

// demo02.go
package main

import "fmt"

func main() {
    var a int32 = 0x0123abcd
    // # 表示列印字首。x, X 表示以小寫或大寫 16 進制列印
    fmt.Printf("%d %x %#x %#X\n", a, a, a, a)
    // [1] 表示再次引用第一個資料
    fmt.Printf("%d %[1]x %#[1]x %#[1]X\n", a)

    var b byte = 'a'
    var c rune = '犇'

    // %c 表示列印 ascii 或 unicode 字元,%q 表示列印字元,外面帶個單引号
    fmt.Printf("%d %c %q\n", b, b, b)
    fmt.Printf("%d %[1]c %[1]q\n", c)
}      
025-整數和浮點數

圖2 整數輸出格式控制

4.2 浮點數輸出

package main

import "math"
import "fmt"

func main() {
    var a float32 = 3.1415926
    var b float64 = 6.02214129e23

    // 浮點數無法使用 %d 控制。
    // %g 表示以更加緊湊的格式輸出,%f 可以控制浮點數輸出的寬度和精度,以及填充符号
    fmt.Printf("%d %g %f\n", a, a, a)
    fmt.Printf("%d %g %f\n", b, b, b)

    // 列印 e 的 0 次方到 7 次方
    for x := 0; x < 8; x++ {
        fmt.Printf("x=%d e^x=%8.3f\n", x, math.Exp(float64(x)))
    }
}      
025-整數和浮點數

圖3 浮點數輸出格式控制

5. 總結

  • 掌握 go 裡的 8 種整數類型
  • 了解符号和有符号的作用,符号在哪裡會産生影響
  • 了解進位和溢出
  • 掌握整數和浮點數的輸出格式控制
  1. 使用 go 程式模拟 11111111 + 00000001 (二進制),然後以有符号和無符号的方式分别列印到螢幕,看看輸出結果。
  2. 解釋 1 中的輸出結果,解理符号的作用。
  3. 在 1 中,哪種算是溢出?