1. 程式人生 > >原碼、反碼、補碼、移碼和數值計算

原碼、反碼、補碼、移碼和數值計算

歡迎訪問我的個人站點,老廖的個人部落格

前言

  計算機的數值編碼和運算應該是本科一年級就會學習的基礎知識。從軟體開發這個角度來說,很多時候這些知識沒有在開發過程中得到有效的利用和實踐。
  不巧,最近在做的一個專案,常常需要從補碼的角度考慮數值表示和相關關係。因此,也就趁此機會簡單的寫一寫。

數值表示

真值,機器數

  機器數就是數值在計算機中的二進位制表示,數值有正負之分,因此機器數用最高位來表示數值的符號,0 代表正數,1 代表負數。根據表示方法不同,機器數分為:原碼、反碼、補碼、移碼等。

  真值就是帶正負的真實十進位制值。

原碼

  原碼很簡單,就是最高位作為符號位,其餘位表示真值的絕對值。

反碼

  正數的反碼就是 其原碼本身負數的反碼則是 原碼除符號位外 ,其餘位取反。

  反碼的運算方法為 迴圈進位,即 最高位的進位要加到最低位來。如:8位為例:11111110(-1) + 11111110(-1) = 11111100 + 1 = 11111101(-2)

  產生的原因: 計算機的所有計算本質上都是加法,然而若正負值相加時讓計算機判斷符號位來選擇運算方式會使得加法電路設計變得複雜,但是若直接讓符號位參與運算則會帶來 1 + (-1) = -2 (8位為例:00000001 + 10000001 = 10000010) 等等問題。反碼的提出就是為了解決 符號位參與運算 的加法問題。

補碼

  反碼是一個不完美的解決方案,有不近人意的問題。比如: 8位為例,00000000 和 11111111

都可以表示零,一個 +0,一個 -0。為了解決這個問題,引入補碼來表示數值。

  正數 的補碼是 其原碼本身負數 的補碼是 其反碼 + 1 。這個設計使得加法運算滿足一個等式:a(補) + b(補) = (a + b)(補) ,由此,不管符號為何,直接參與運算都能得到正確的結果。目前補碼是最佳的解決方案。 現行的程式語言,都是用補碼來表示數值和進行算數運算

  設計的原理

  設計補碼的原因是要解決表示反碼錶示 0 的問題。

  計算機表示數值是有範圍的,取決於用多少 bit 來表示,當加法計算超過了 bit 的長度,就會產生溢位 ,溢位的部分消失,這和 模運算 恰好一致。比如上限是 255,則 5 mod 255 = 5,260 mod 255 = 5。

我們可以說,在8位的情況下,5 和 260 是等價的。同理,在負數的情況下 (反向理解),我們可以用負數的模除值等價負數,這樣相當於用正數 (負數的模除值為正數) 表示了負數。

  根據模運算的運演算法則,(a+b) mod m = [(a mod m) + (b mod m)] mod m,在 m 為最大值的情況下,數 mod m 的值就是 的反碼。反碼 + 1 等價於 m + 1 並不破壞等式,同時保證了符號的正確和 0 的正確表示。

移碼

  移碼則是 把補碼的符號位取反,常常用在 浮點 數值的二進位制表示中。

浮點的二進位制表示

  浮點數的二進位制表示比較特殊,整個二進位制位分為三個部分:

型別 符號位 階碼 尾數 總位數
float 1 8 23 32
double 1 11 52 64

  設階碼為 e,尾數為 m,則浮點的值為: $ m * 2^e\quad $,其中階碼是用移碼錶示。

  用移碼錶示的原因在於,階碼作為指數是有正負的,用移碼錶示能在 不考慮符號 的情況下比較浮點數大小。如:(8位情況下) 11111111 是最小值,00000000 是 0 如果不考慮符號,則 11111111 > 00000000 顯然不方便。而用移碼則:11111111 -> 00000001,00000000 -> 10000000 大小一下就比較出來了。

  階碼確定浮點數的取值範圍,尾數確定浮點數的精度

數學運算

  上面也說到了,計算機的數值運算是 通過補碼完成的 。兩個數的補碼進行加法運算得到最終的值。若要顯示成人類可讀,則通過補碼的定義轉換成真值。

整型溢位

  數值運算時極易發生整型溢位,直接通過補碼的變化來判斷溢位是否發生是非常準確的。

  我們把最高位 (符號位) 緊挨著的那一位稱為最高有效位,那麼,若符號位和最高有效位只有一個發生了進位,則出現了整型溢位。