二進制與 Go 的原子操做

二進制與 Go 的原子操做

前置閱讀:html

二進制相關基礎概念

有符號二進制整數有正數和負數。在 x86 處理器中,MSB 表示的是符號位:0 表示正數,1 表示負數。下圖展現了 8 位的正數和負數:編程

image

概念總結:segmentfault

  • 反碼、補碼是二進制的一種表現形式;
  • 在計算機內全部數值底層都用補碼錶示,不管正負數(十進制);
  • 若是一串二進制值須要視爲數值則須要將其視爲補碼;
  • 反碼是十進制轉二進制計算的一個過程即對一個十進制取補碼的過程,通常用在負數轉換規則上;
  • 反碼能夠經過二進制值按位取反獲得(全部二進制位都取反);
  • 正數(十進制)的補碼是其二進制自己,負數(十進制)的補碼是十進制負數的絕對值求補碼後取反碼加一;
  • 表示正數的補碼能夠直接轉成十進制,表示負數的補碼想要轉回十進制步驟以下:安全

    • 對錶示負數的補碼取反碼加一獲得負數的十進制絕對值補碼;
    • 再將負數的十進制絕對值補碼轉成十進制獲得負數的十進制絕對值;
    • 最後加上符號位;
  • 不管是正數加正數(十進制加法)仍是正數/負數加負數(十進制減法)均可以用補碼加補碼錶示;
  • 一個值的正數的補碼與其負數的補碼相加等於 0;

反碼

反碼能夠經過二進制值按位取反獲得(全部二進制位都取反)

正數的反碼示例:併發

十進制數值 補碼 反碼
0 0000 0000 1111 1111
1 0000 0001 1111 1110
2 0000 0010 1111 1101
3 0000 0011 1111 1100
4 0000 0100 1111 1011

負數的反碼示例:函數

十進制數值 補碼 反碼
-0 0000 0000 1111 1111
-1 1111 1111 0000 0000
-2 1111 1110 0000 0001
-3 1111 1101 0000 0010

補碼(十進制轉二進制)

在計算機內全部數值底層都用補碼錶示,不管正負數(十進制)
十進制數值 補碼
0 0000 0000
1 0000 0001
2 0000 0010
3 0000 0011
-0 0000 0000
-1 1111 1111
-2 1111 1110
-3 1111 1101

負數補碼計算過程示例:ui

十進制數值 絕對值 絕對值補碼 絕對值補碼取反 絕對值補碼取反加一 正確補碼 十進制數值
-0 0 0000 0000 1111 1111 1111 1111
+ 1
—————
1,0000 0000
0000 0000 -0
-1 1 0000 0001 1111 1110 1111 1110
+ 1
—————
1111 1111
1111 1111 -1
-2 2 0000 0010 1111 1101 1111 1101
+ 1
—————
1111 1110
1111 1110 -2
-3 3 0000 0011 1111 1100 1111 1100
+ 1
—————
1111 1101
1111 1101 -3
-4 4 0000 0100 1111 1011 1111 1011
+ 1
—————
1111 1100
1111 1100 -4
-5 5 0000 0101 1111 1010 1111 1010
+ 1
—————
1111 1011
1111 1011 -5

補碼(二進制轉十進制)

表示正數的補碼能夠直接轉成十進制,表示負數的補碼想要轉回十進制步驟以下:atom

  • 對錶示負數的補碼取反碼加一獲得負數的十進制絕對值補碼;
  • 再將負數的十進制絕對值補碼轉成十進制獲得負數的十進制絕對值;
  • 最後加上符號位;
MSB 補碼 十進制數值
0 0000 0000 0
0 0000 0001 1
0 0000 0010 2
0 0000 0011 3
0 0000 0100 4
0 0000 0101 5
1 1111 1111 -1
1 1111 1110 -2
1 1111 1101 -3
1 1111 1100 -4
1 1111 1011 -5

負數轉換示例:spa

MSB 補碼 補碼取反 補碼取反加一 補碼取反加一後所表明十進制值 符號 十進制結果 補碼
1 1111 1111 0000 0000 0000 0001 1 - -1 1111 1111
1 1111 1110 0000 0001 0000 0010 2 - -2 1111 1110
1 1111 1101 0000 0010 0000 0011 3 - -3 1111 1101
1 1111 1100 0000 0011 0000 0100 4 - -4 1111 1100
1 1111 1011 0000 0100 0000 0101 5 - -5 1111 1011

補碼相加

不管是正數加正數(十進制加法)仍是正數/負數加負數(十進制減法)均可以用補碼加補碼錶示

正數加正數的補碼計算過程示例:.net

表達式 補碼相加 二進制結果 十進制結果
0+0 0000 0000
+ 0000 0000
——————
0000 0000
0000 0000 0
0+1 0000 0000
+ 0000 0001
——————
0000 0001
0000 0001 1
1+1 0000 0001
+ 0000 0001
——————
0000 0010
0000 0010 2
2+1 0000 0010
+ 0000 0001
——————
0000 0011
0000 0011 3

正數加負數的補碼計算過程示例:

表達式 補碼相加 二進制結果 十進制結果
0+(-0) 0000 0000
+ 0000 0000
——————
0000 0000
0000 0000 0
0+(-1) 0000 0000
+ 1111 1111
——————
1111 1111
1111 1111 -1
1+(-1) 0000 0001
+ 1111 1111
——————
1,0000 0000
0000 0000 0
1+(-2) 0000 0001
+ 1111 1110
——————
1111 1111
1111 1111 -1
2+(-2) 0000 0010
+ 1111 1110
——————
1,0000 0000
0000 0000 0
2+(-1) 0000 0010
+ 1111 1111
——————
1,0000 0001
0000 0001 1

負數加負數的補碼計算過程示例:

表達式 補碼相加 二進制結果 十進制結果
(-0)+(-0) 0000 0000
+ 0000 0000
——————
0000 0000
0000 0000 0
(-1)+(-1) 1111 1111
+ 1111 1111
——————
1,1111 1110
1111 1110 -2
(-1)+(-2) 1111 1111
+ 1111 1110
——————
1,1111,1101
1111 1101 -3

二進制、反碼、補碼

一樣的一串二進制數字,便可以是反碼也能夠是補碼,若是是補碼則其能夠經過上述規則轉成對應的十進制數值,若是是反碼則表明其爲計算過程當中間值,若是想知道反碼在十進制中所表示的數值,能夠將其視爲補碼再經過上述規則轉成十進制便可。

正數示例:

十進制數值 x x 取補碼 fn1(x)=a x 取反碼 fn2(x)=b b 的十進制形式 y
0 0000 0000 1111 1111 -1
1 0000 0001 1111 1110 -2
2 0000 0010 1111 1101 -3
3 0000 0011 1111 1100 -4
4 0000 0100 1111 1011 -5

負數示例:

十進制數值 x x 取補碼 fn1(x)=a x 取反碼 fn2(x)=b b 的十進制形式 y
-0 0000 0000 1111 1111 -1
-1 1111 1111 0000 0000 0
-2 1111 1110 0000 0001 1
-3 1111 1101 0000 0010 2

示例彙總:

十進制數值 x x 取補碼 fn1(x)=a x 取反碼 fn2(x)=b b 的十進制形式 y y + 1 十進制數值 x
0 0000 0000 1111 1111 -1 0 0
1 0000 0001 1111 1110 -2 -1 1
2 0000 0010 1111 1101 -3 -2 2
3 0000 0011 1111 1100 -4 -3 3
-0 0000 0000 1111 1111 -1 0 -0
-1 1111 1111 0000 0000 0 1 -1
-2 1111 1110 0000 0001 1 2 -2
-3 1111 1101 0000 0010 2 3 -3

經過該表格示例能夠得出如下兩個規律:

規律 一

反碼所表示的數值與原數值之間規律以下(y 表明反碼以後的十進制值):

  • fn2(x) = -x-1
  • fn2(x) + 1 = -x
  • y = -x-1
  • y +1 = -x

即若是想獲得一個十進制正數值的負數形式(1 => -1)或則獲得一個十進制負數值的正數形式能夠經過對原值取反碼加一獲得:

十進制數值 x 十進制取反 -x 過程
0 0 取反碼(0)+1 = -1+1
1 -1 取反碼(1)+1 = -2+1
2 -2 取反碼(2)+1 = -3+1
3 -3 取反碼(3)+1 = -4+1
-1 1 取反碼(-1)+1 = 0+1
-2 2 取反碼(-2)+1 = 1+1
-3 3 取反碼(-3)+1 = 2+1

規律 二

將示例彙總表格再進一步簡化:

十進制數值 x x 的反碼十進制表示形式 y 翻譯 -1 翻譯 -2
0 -1 0 的反碼是 -1 -1 是 0 的反碼
1 -2 1 的反碼是 -2 -2 是 1 的反碼
2 -3 2 的反碼是 -3 -3 是 2 的反碼
3 -4 3 的反碼是 -4 -4 是 3 的反碼
-0 -1 -0 的反碼是 -1 -1 是 -0 的反碼
-1 0 -1 的反碼是 0 0 是 -1 的反碼
-2 1 -2 的反碼是 1 1 是 -2 的反碼
-3 2 -3 的反碼是 2 2 是 -3 的反碼

能夠看出在十進制格式下,原數值與反碼的關係:

  • 若是我須要 -1 我能夠用 0 的反碼代替;
  • 若是我須要 -4 我能夠用 3 的反碼代替;
  • 規律:

    • x = |y| -1
    • x + y = -1

Go 的表現

二進制的輸出格式

在 Go 語言中,一個數值是正數或負數,不管是何種打印方式,輸出的都會待上正負號:

fmt.Printf("1 的十進制          : %v\n",1)
fmt.Printf("-1 的十進制          : %v\n",-1)
fmt.Printf("-1 的二進制(簡化版): %v\n",strconv.FormatInt(-1,2))
fmt.Printf("1 的二進制          : %064b\n",1)     // 佔 64 位寬,不足補 0
fmt.Printf("-1 的二進制          : %064b\n",-1)    // 佔 64 位寬,不足補 0


fmt.Printf("4 的十進制          : %v\n",4)
fmt.Printf("-4 的十進制          : %v\n",-4)
fmt.Printf("-4 的二進制(簡化版): %v\n",strconv.FormatInt(-4,2))
fmt.Printf("4 的二進制          : %064b\n",4)        // 佔 64 位寬,不足補 0
fmt.Printf("-4 的二進制          : %064b\n",-4)    // 佔 64 位寬,不足補 0

//  輸出
//  1 的十進制         : 1
// -1 的十進制         : -1
// -1 的二進制(簡化版): -1
//  1 的二進制         : 0000000000000000000000000000000000000000000000000000000000000001
// -1 的二進制         : -000000000000000000000000000000000000000000000000000000000000001


//  4 的十進制         : 4
// -4 的十進制         : -4
// -4 的二進制(簡化版): -100
//  4 的二進制         : 0000000000000000000000000000000000000000000000000000000000000100
// -4 的二進制         : -000000000000000000000000000000000000000000000000000000000000100
  • 能夠看出輸出二進制時 Go 的輸出與十進制同樣一樣將符號位具象化,而非輸出對應的 0 或 1;
  • 輸出二進制時數值部分則取其絕對值的補碼;

若是咱們想要看到正確的負數的補碼形式則須要經過無符號數值類型間接實現:

  • 無符號數值類型如何表示一個數的負數形式,答案是補碼取反碼加一;
  • 譬如如何用無符號數值類型表示 -1: ^uint8(1) + 1
  • ^ 符號在二元運算中表明亦或符;在一元運算中表明取反碼符;
fmt.Printf("int8(1)     :     %08b \n", int8(1))    // 佔 8 位寬,不足補 0
fmt.Printf("^int8(1)    :     %08b \n", ^int8(1))    // 佔 8 位寬,不足補 0
fmt.Printf("^int8(1)+1  :     %08b \n", ^int8(1)+1)    // 佔 8 位寬,不足補 0

fmt.Printf("uint8(1)    :     %08b \n", uint8(1))    // 佔 8 位寬,不足補 0
fmt.Printf("^uint8(1)   :     %08b \n", ^uint8(1))    // 佔 8 位寬,不足補 0
fmt.Printf("^uint8(1)+1 :     %08b \n", ^uint8(1)+1)// 佔 8 位寬,不足補 0

//  輸出
// int8(1)     :     00000001 
// ^int8(1)    :     -0000010 
// ^int8(1)+1  :     -0000001 

// uint8(1)    :     00000001 
// ^uint8(1)   :     11111110 
// ^uint8(1)+1 :     11111111
  • 能夠看到經過使用無符號數值類型對 1 取反後獲得的是 -2 的補碼形式 1111 1110,接着對反碼後加一獲得原值 1 的負數形式 -1 的補碼 1111 1111

Go 的原子操做

不管是有符號數值類型仍是無符號數值類型,只要轉成補碼後進行的計算過程不須要考慮符號位的問題。

A - B = A + ( -B )

A - B = 補碼( A ) + 補碼( -B )

原子操做便是進行過程當中不能被中斷的操做。也就是說,針對某個值的原子操做在被進行的過程中,CPU 毫不會再去進行其它的針對該值的操做。不管這些其它的操做是否爲原子操做都會是這樣。爲了實現這樣的嚴謹性,原子操做僅會由一個獨立的 CPU 指令表明和完成。只有這樣纔可以在併發環境下保證原子操做的絕對安全。

Go 語言提供的原子操做都是非侵入式的。它們由標準庫代碼包 sync/atomic 中的衆多函數表明。咱們能夠經過調用這些函數對幾種簡單的類型的值進行原子操做。這些類型包括 int3二、int6四、uint3二、uint6四、uintptr 和 unsafe.Pointer 類型,共 6 個。這些函數提供的原子操做共有 5 種,即:增或減、比較並交換、載入、存儲和交換。

原子操做 - 增或減

相關文檔如圖所示:

image.png

這裏主要須要注意的是 Uint 類型的原子操做,以 AddUint32 函數爲例

原子性的增長數值:

value := uint32(1)

atomic.AddUint32(&value, 1)
fmt.Printf("after call atomic.AddUint32(&value, 1) value is: %v\n", value)

atomic.AddUint32(&value, 2)
fmt.Printf("after call atomic.AddUint32(&value, 2) value is: %v\n", value)

atomic.AddUint32(&value, 3)
fmt.Printf("after call atomic.AddUint32(&value, 3) value is: %v\n", value)

// 輸出
// after call atomic.AddUint32(&value, 1) value is: 2
// after call atomic.AddUint32(&value, 2) value is: 4
// after call atomic.AddUint32(&value, 3) value is: 7

原子性的減小數值:

如文檔所述,若是須要減去一個正數 c 須要經過 ^uint32(c-1) 計算獲得 c 的補碼。

const one, two, three = 1, 2, 3
value := uint32(10)

atomic.AddUint32(&value, ^uint32(one - 1)) // 減一
fmt.Printf("after callatomic.AddUint32(&value, ^uint32(one - 1))    value is: %v\n", value)

atomic.AddUint32(&value, ^uint32(two - 1)) // 減二
fmt.Printf("after callatomic.AddUint32(&value, ^uint32(two - 1))    value is: %v\n", value)

atomic.AddUint32(&value, ^uint32(three - 1)) // 減三
fmt.Printf("after callatomic.AddUint32(&value, ^uint32(three - 1))  value is: %v\n", value)

// 輸出
// after callatomic.AddUint32(&value, ^uint32(one - 1))    value is: 9
// after callatomic.AddUint32(&value, ^uint32(two - 1))    value is: 7
// after callatomic.AddUint32(&value, ^uint32(three - 1))  value is: 4
  • value -1 等價於 value + (-1) ,等價於 補碼( value ) + 補碼( -1 );
  • 經過前面二進制規律二 得知,求 -1 的補碼至關於求 0 的反碼;
  • go 的反碼運算符位 ^
  • 結合後即可實現了無符號類型數據的減法運算。

參考

相關文章
相關標籤/搜索