1. 程式人生 > >編碼的奧祕:定點數和浮點數

編碼的奧祕:定點數和浮點數

轉自:《編碼的奧祕》    第二十三章

 

 

           日常生活中,有各種各樣的數,整數、分數、百分數等等,我們無時無刻不與這些數打交道。如:用加班 2 . 7 5小時獲得的 1 倍半的錢來買半匣雞蛋需支付 8 . 2 5 %的銷售稅。許多人對諸如此類的數都感到很適應,並不需要怎麼在行,即使在聽到“平均每個美國家庭有 2 . 6人”這樣的統計數字的時候,也不會聯想到 2 . 6這個數字對人來說是不是要把人肢解了這樣可怕的問題。

           在計算機記憶體裡,整數和分數的換算是常見的。存在計算機記憶體裡的東西都是二進位制位的形式,也就是說,都是二進位制數。但有些數用位來表示比其他數用位來表示要容易一些。

           我們使用位來表示數學上稱為自然數而計算機程式設計人員稱為正整型數的數,並介紹如何用 2的補碼來表示負整數,而這種方法很容易實現正數、負數的加法。下表列出了 8位、 1 6位、3 2位的正整數及它們的 2的補碼的範圍:

                           要介紹的就是這些。除了整數以外,數學上還定義了有理數,它們可表示成兩個整數的比,這個比也叫分數。例如, 3 / 4是一個有理數,因為它是 3 與4的比。可以把這個數寫成小數形式0 . 7 5,當寫成小數時,它真正表示了分數,在此為 7 5 / 1 0 0。

           回憶一下第 7章裡的小數系統,在小數點左邊的數字與 1 0的整數次冪相關聯;同樣,在小數點右邊的數字與 1 0的負整數次冪相關聯。第 7章用 42 705.684作為例子,該數可以表示成與下面與之相等的形式:

           

          注意一下除號,可以把這個序列寫成沒有除號的形式:

          最後,可以用 1 0的冪的形式表示如下:

           有些分數並不容易用小數表示,常見的如 1 / 3。如果用 3 去除1 ,可以得到:

而永無止境。我們通常寫成簡潔形式,在 3 上面加一道橫線來表示無限迴圈:

即使這樣,把 1 / 3寫成小數也是有些笨拙的。它還是一個分數,因為它是兩個整數的比。同樣,1 / 7是:
            無理數則更不同,如 2的平方根。無理數不能表示成兩個整數的比,也就是說,小數部分是無窮的,沒有重複規律或固定模式:

 

2的平方根是下面這個代數方程的根:
           如果一個數不是以整數為係數的代數方程的根,則稱為超越數 (所有的超越數為無理數,但並不是所有的無理數都是超越數)。超越數包括π,它是圓的周長與直徑的比,近似值為:

另一個超越數是е,它是下面表示式:

         當n趨近於無窮大時的近似值:
          到現在為止,談到的所有數—有理數和無理數—統稱為實數。這種定義用來與虛數相區分。虛數是負數的平方根,複數是由虛數和實陣列成的。不管名稱如何,虛數揭示了現實世界的奧祕,可以用來(例如)解決電子學的一些高階問題。

          習慣上,我們把數看成是連續的。如果給出兩個有理數,則可以找出一個數在這兩個數中間。實際上,只需取平均值即可。但是,數字計算機不能處理連續事件。位不是 0就是 1 ,沒有中間值。由於這一特性,數字計算機必須處理離散值 。可以表示的離散值的個數直接與可達到的二進位制位數相關。例如:如果用 3 2位來存放正整數,則可以存放 0~4 294 967 295 個整數。如果需要存放 4 . 5這個數,則必須重新考慮一種方法並做一些改動。

          小數可以表示成二進位制嗎?是的,可以。最容易的方法可能是二進位制編碼的十進位制( B C D)。前面第 1 9章講到 B C D是十進位制數的二進位制編碼,每一個十進位制數字( 0、 1 、 2、 3 、4、 5、 6、 7、 8和9)需要 4位,如下表所示:

           B C D碼特別適用於用美元和美分表示的與錢數有關的計算機程式。銀行和保險公司是兩個典型的多與錢打交道的行業,對這類公司的計算機程式來說,許多分數只需要兩個十進位制數位。

           通常1 個位元組儲存兩個 B C D數字,有時將這稱為壓縮B C D碼。 2的補碼不與 B C D一起使用,因此,壓縮 B C D碼通常要有額外的一位 (稱作符號位 )來標明是正數還是負數。一個 B C D數存入整個位元組比較方便,所以,小小的符號位通常需要犧牲 4位或8位的儲存空間。

           來看一個例子。假定計算機要處理的錢數不會超過正、負 1 0 0 0萬,換句話說,需要表示的錢數的範圍從- 9 999 999.99~9 999 999.99,則儲存在記憶體的每一筆錢數需要用 5個位元組來表示。例如,- 4 325 120.35用 5個位元組表示為:

            

或用十六進位制表示為:


            注意最左邊的 1 用來表示負數,即符號位。如果是正數,則該位為 0。每一個數字需要 4位,從十六進位制值中可以直接看到。

            如果需要表示的數的範圍從- 99 999 999.99 ~99 999 999.99,則需要 6個位元組—1 0個數字佔 5個位元組,另一個位元組僅用來表示符號位。

             這類儲存和標記方法也稱作定點格式 ,因為小數點通常固定在特定的位置—本例中,小數點在兩個小數位之前。注意,實際上並沒有什麼東西與數一起存放用來標明小數點的位置。處理定點格式數的程式應該知道小數點在哪裡。定點數可以有任意個小數位數,在同一計算機程式裡可以混用這些數字,但是對這些數進行算術運算的那部分程式必須知道小數點的位置。

             定點格式只在知道這些數不會超過預先確定的記憶體單元,且沒有太多小數位的場合比較適用。在數可能很大或可能很小的場合定點格式完全不適用。假設保留一個記憶體區域用來儲存以英尺為單位的距離,則存在的問題是距離可能超出範圍。從地球到太陽的距離是490 000 0 0 0 0 0 0英尺,氫原子的半徑為 0 . 0 0 0 0 0 0 0 0 0 2 6英尺,則你需要 1 2位元組的定點儲存空間來容納這些可能很大也可能很小的數值。

             如果你還記得科學家和工程師們喜歡用稱為“科學記數法”的系統來表示數的話,你也許已找到更好的儲存此類數的方法。科學記數法特別適用於表示很大和很小的數,因為它採用 1 0的冪方法從而不用寫很長的一串 0。採用科學記數法後,數字

            數字

           在這兩個例子裡,數字 4 . 9和2 . 6稱作小數部分或首數,有時也稱作有效數 (儘管這個詞更適用於對數運算)。為了與計算機術語相協調,在這兒把科學記數法的這一部分稱作有效數。

           指數部分是 1 0的冪。在第一個例子中,指數是 11 ;在第二個例子中,指數是- 1 0。指數用來指明有效數的小數點要移動的位數。

           為方便起見,有效數通常大於或等於 1 而小於 1 0。儘管下面的數字是相等的:

 

但我們選用第一種格式。這種格式也稱作科學記數法的規格化格式 。

          注意,指數符號只是標明數的大小而並不表示數本身是正的還是負的。下面是用科學記數法表示的兩個負數的例子:

           和

           在計算機中,對應於定點表示法的是浮點表示法。浮點格式用來儲存較小或較大的數比較理想,因為它是以科學記數法為基礎的。但是,計算機中採用的浮點格式是用科學記數法表示的二進位制數。這裡首先要提到的是如何用二進位制表示小數數字。

          實際上,這比設想的要容易,在十進位制表示中,小數點右邊的數字具有 1 0的負整數次冪;在二進位制表示中,二進位制小數點(也僅是一個點,看起來與十進位制小數點一樣)右邊的數具有 2 的負整數次冪。例如,一個二制數:

可以用以下表達式轉換成十進位制:

 

除號可以用 2 的負整數次冪替換:

 

或者, 2的負整數次冪可以從 1 開始重複除以 2來計算:

 

通過這些計算得到 1 0 1 . 11 0 1 等效的十進位制數 5 . 8 1 2 5。

            在十進位制科學記數法中,規格化有效數通常大於或等於 1 而小於 1 0。同樣,二進位制科學記數法的規格化有效數也通常大於或等於 1 而小於 1 0(即十進位制中的 2)。所以,按二進位制科學記數法,數

           這個規則隱含了一件有趣的事實:通常二進位制浮點數在二進位制小數點的左邊除了 1 以外再沒有別的了。

           現代計算機和計算機程式按照 I E E E在1 9 8 5 年制定的標準來處理浮點數,這個標準也為ANSI(the American national standards institute ,美國國家標準局 )所認可。 A N S I / I E E E  S t d7 5 4- 1 9 8 5稱作 I E E E二進位制浮點數算術運算標準 。它並不像一般標準那樣長,只有 1 8頁,但卻奠定了以方便的方式編碼二進位制浮點數的基礎。

           I E E E浮點數標準定義了兩個基本格式:單精度格式,需要 4個位元組;雙精度格式,需要 8個位元組。

           首先看一下單精度格式,它有三部分: 1 位個符號位( 0表示正, 1 表示負) 、 8位的指數位和2 3位的有效數位。如下所示,最低有效數在最右邊:

           總共有 3 2位, 4個位元組。因為規格化二進位制浮點數的有效數通常在二進位制小數點左邊為 1 ,所以在 I E E E格式中這一位不包含在浮點數的儲存空間中。有效數的 2 3位小數部分是反被儲存的部分,所以,即使只有 2 3 位用來儲存有效數,精度仍然認為是 2 4位的。過一會兒將要看到2 4位精度的意義。

            8位指數範圍從 0~ 2 5 5,稱為 移碼指數,意思是必須從指數中減去一個數(稱為 偏移量)才能確定有符號指數的實際值。對單精度浮點數,偏移量為 127 。

            指數0和2 5 5用於特殊用途,在此簡單描述一下。如果指數從 1 變化到 2 5 4,則由s(符號位) 、e(指數)和 f(有效數)來表示的數為:

            - 1 的s次冪是數學上的一種方法,意思是“如果 s為0,則數是正的(因為任何數的 0次冪等於1 );如果 s為1 ,則數是負的(因為- 1 的1 次冪為- 1 )”。

             表示式的另一部分是 1 . f,意思是 1 後面為二進位制小數點,再後面為 2 3 位的有效小數部分。它乘以 2的冪,其中指數為記憶體中的 8位移碼指數減去 1 2 7。

             注意,到現在還沒有提到如何表示一個很常見的數字,那就是 0。這是一種特殊情況,即:

             • 如果e等於0,且f等於0,則數為0。通常,所有3 2位均為 0則表示0。但是符號位可以是 1 ,在這種情況下,數被解釋為                 - 0。- 0可以表示一個很小的數,小到在單精度格式中不能用數字和指數來表示。儘管如此,它們然小於 0。
             • 如果e等於0,且f不等於 0,則數是有效的。但是,它不是規格化的數,它等於(- 1 ) s× 0 . f× 2 - 1 2 7

            注意,二進位制小數點左邊的有效數為 0。

 

             • 如果e等於2 5 5,且f等於0,則數為正或負無窮大,這取決於符號 s。
             • 如果e等於2 5 5,且f不等於 0,該值被認為“不是一個數”,簡寫為 N a N。 N a N可以表示一個不知道的數或者一個無效                   操作的結果。

             通常,單精度浮點格式中可以表示的最小規格化的正或負二進位制數為:

            在二進位制小數點之後有 2 3個0。在單精度浮點格式中可以表示的最大規格化的正或負二進位制數為:

            換算或十進位制,這兩個數近似為。這就是單精度浮點數表示法的有效範圍。

            前面講過, 1 0位二進位制數近似等於 3 位十進位制數。也就是說,若 1 0位都置 1 (即十六進位制為3 F F h,十進位制為 1 0 2 3 ),則它近似等於 3位十進位制都設定為 9,即 9 9 9。或者

             這種關係表明按單精度浮點格式存放的 2 4位二進位制數大約與 7位十進位制數等效。因此,也可以說單精度浮點格式提供 2 4位二進位制精度,或大約 7位十進位制精度。它的含義是什麼呢?

             當觀察定點數的時候,數的精度是很顯然的。例如,對於錢數,用兩位十進位制小數的定點數就可精確到分。但是,對浮點數來說,就不能這麼肯定了。根據指數值的不同,有時浮點數可以精確到比分還小的單位,有時甚至不能精確到元。

             粗略地講,單精度浮點數可精確到 ,或1 / 1 6 7 7 7 2 1 6,或約百萬分之六。這到底是什麼意思呢?

             從某種意義上講,它意味著如果想用單精度浮點數來表示 16 777 216和16 777 217,其結果是一樣的。而且,在這兩個數之間的任何數(如 16 777 216.5 )也認為是與它們一樣的。所有這3個十進位制數都按 3 2位單精度浮點數

 

來存放。當把此數分成符號位,指數和有效數位時,如下所示:

也即為

 

下一個表示的最大有效數是 16 777 218,它的二進位制浮點表示為:

            兩個不同的十進位制數卻以相同的浮點數存放可能是也可能不是一個問題。

            如果是為銀行編寫程式且用單精度浮點數來儲存元、分等,則你可能會很苦惱地發現$262 144.00與$262 144.01 是一樣的。兩個數字都是:

這就是當處理元、分的時候,為什麼要用定點數的原因。在處理浮點數的時候,可能還會發現其他足以使人發瘋的小毛病。程式原本計算的結果是 3 . 5 0卻成了 3 . 4 9 9 9 9 9 9 9 9 9 9 9。浮點計算中這種事情經常發生,但也沒有別的更好的處理方法。

            如果想用浮點表示法,又不想出現單精度那樣的問題,可以用雙精度浮點格式。這樣的數需要 8個位元組來存放,格式如下:

 

指數偏移量為 1 0 2 3,即 3 F F h,所以,以這種格式存放的數為

它具有與單精度格式中所提到適用於 0、無窮大和 N a N等情形相同的規則。

          最小的雙精度浮點格式的正數或負數為

          用十進位制表示,它的範圍近似為。 1 0的3 0 8次冪是一個非常大的數,在 1 後面有 3 0 8個十進位制零。

          5 3 位有效數(包括沒有包含在內的那 1 位)的精度與 1 6個十進位制位表示的精度十分接近。相對於單精度浮點數來說這種表示要好多了,但它仍然意味著最終還是有一些數與另一些數是相等的。例如, 140 737 488 355 328.00與140 737 488 355 328.01 是相同的,這兩個數按照6 4位雙精度浮點格式儲存,結果都是:

可把它轉換為

         當然,開發一種格式用來在儲存器中儲存浮點數只是在組合語言程式中實際使用這些數的工作中的一小部分。如果真的要研製與世隔絕的計算機,則你需要面對編寫浮點數的加、減、乘、除的函式集的工作。幸運的是,這些工作可以被分解成許多小的只涉及到整數的加、減、乘、除的工作,而整數的四則運算我們已經知道如何實現了。

          例如,浮點加法的關鍵是有效數相加,因而用的技巧是用兩個數的指數部分確定有效數如何移位。假設要做以下加法:

         需要把 11 1 0 1 與 1 0 0 1 0相加,但不是就這樣相加。指數部分的不同表明第二個數必須進行移位。實際上,需要進行 111 0 1 0 0 0和1 0 0 1 0的整數加法。最後的和是:

         有時兩個數的指數部分差距很大,其中一個數甚至對和沒有影響。就像這種情況:把地球到太陽的距離與氫原子的半徑相加。

         兩個浮點數的相乘是把有效數部分像整型數那樣相乘並把兩個整型指數相加。通常,規格化有效數部分可能會引起對新的指數調整一、二次。

         浮點算術運算中另一個複雜問題牽涉到較麻煩的計算,如方根、冪、對數和三角函式。但是,所有這些工作都可以用四個基本的浮點操作:加、減、乘、除來完成。

         例如,三角函式 S i n可以通過下列展開式來計算,如下:

          引數x必須是弧度, 3 6 0度的弧度為 2p。感嘆號是階乘符號,其含義是把 1 到該數之間的所有整數相乘,如: 5 ! = 1 × 2× 3× 4× 5。這只是進行乘法運算,其中每一項的指數部分也是乘法。其餘的是一些除法、加法和減法。唯一真正麻煩的部分是在最後的省略,它意味著要永遠地計算下去。然而,實際上,如果侷限在 0~ p / 2的範圍(從這裡可以推匯出所有其他的正弦函式值),並不需要進行多少展開運算。在展開大約 1 2項以後,已經精確到了雙精度數的 5 3位。

           當然,使用計算機是為了使人們更容易完成某些工作,所以,編寫浮點運算程式這樣的工作似乎離使用計算機的目的相差甚遠。然而,這正是軟體的可愛之處:一旦某人為某臺機器編寫了浮點運算程式,其他人都可以使用。對科學和工程應用程式來說,浮點運算非常重要,所以通常有很高的優先權。在計算機出現的早期,一旦新的型別的計算機出來,編寫浮點運算程式通常是第 1 項軟體工作。

            事實上,甚至可以設計計算機機器碼指令直接進行浮點運算!顯然,說起來容易做起來難,但這也說明了浮點運算的重要性。如果可以用硬體來實現浮點運算— 與 1 6位微處理器的乘法和除法指令一樣—則計算機中所有浮點運算工作將會完成得更快。

           最早把浮點運算硬體作為選件的商用計算機是 1 9 5 4年的 IBM 704, 7 0 4把所有的數按 3 6位來儲存。對浮點數,分成 2 7位的有效數、 8位指數和 1 個符號位。浮點運算硬體可做加法、減法、乘法和除法,其他浮點運算功能必須用軟體來實現。

           桌面機的浮點運算硬體出現在 1 9 8 0年,當時 I n t e l釋出了 8 0 8 7數字資料協處理器晶片,一種積體電路晶片,今天通常稱為 數學協處理器 或浮點運算單元 ( floating-point unit, F P U)。8 0 8 7之所以稱為協處理器是因為它不能自己單獨使用,它只能與 8 0 8 6或8 0 8 8一起使用, 8 0 8 6和8 0 8 8是I n t e l的第一個 1 6位微處理器。

           8 0 8 7有4 0個引腳,使用許多與 8 0 8 6和8 0 8 8相同的訊號。微處理器和數學協處理器通過這些訊號連線起來。當 C P U收到一個特殊指令—稱為 E S C,代表 E s c a p e—則協處理器接管系統控制權並執行下一條機器程式碼,即包括三角運算、指數、對數運算的 6 8 條指令中的一條。資料型別以 I E E E標準為基礎。那時, 8 0 8 7被認為是所生產的最高階的積體電路。

           可以認為協處理器是一個小的自包含的計算機。在響應某個浮點運算機器碼指令時(例如,計算平方根的 F S Q RT指令) ,協處理器內部執行存放在 R O M中的自己的指令序列,這些內部指令稱為微程式碼。這些指令通常是迴圈的,所以計算結果並不是馬上可用。儘管如此,一般來說,數學協處理器至少比用軟體來實現的同樣例程要快 1 0倍。

           初始的 IBM PC主機板在 8 0 8 8晶片的右邊有一個 4 0管腳的插槽供 8 0 8 7用。遺憾的是,這個插槽是空的,需要加速浮點運算的使用者必須單獨購買 8 0 8 7並自己把它安裝上。即使在安裝了數學協處理器後,並不是所有的應用程式都可以執行得更快,一些應用程式—如,文書處理程式— 幾乎不需要浮點運算。其他如電子報表程式則要用到很多浮點計算。這些程式能夠執行得更快,但並不是所有程式都是如此。

         可以看到,程式設計師必須用協處理器機器碼指令來編寫特定的程式碼供協處理器執行。因為數學協處理器不是硬體的標準部分,因而許多程式設計師怕麻煩不願意做。但是,他們還是不得不編寫自己的浮點運運算元程式(因為許多人並沒有安裝數學協處理器),所以支援 8 0 8 7晶片就成為一個額外的負擔— 一個不小的負擔。最終,如果他們程式執行的機器上有數學協處理器,程式設計師要學會編寫利用數學協處理器的應用程式;如果沒有,則要編寫浮點運算模擬程式。

          經過幾年後, I n t e l還發布了用於 2 8 6晶片的 2 8 7數學協處理器,用於 3 8 6的 3 8 7數學協處理器。但對於 1 9 8 9年釋出的 Intel 486DX, F P U已經做在了 C P U裡面,而不再是作為一個選件!遺憾的是, 1 9 9 1年I n t e l釋出了一種低價格的 4 8 6 S X,它沒有把 F P U做在C P U裡面,而是提供了4 8 7 S X數學協處理器作為一個選件。 1 9 9 3 年釋出的 P e n t i u m晶片卻再一次使做在 C P U內部的F P U成為標準,也許以後永遠會這樣。 M o t o r o l a在它的 6 8 0 4 0微處理器裡集成了 F P U,該微處理器於 1 9 9 0年釋出。以前, M o t o r o l a銷售6 8 8 8 1 和6 8 8 8 2數學協處理器用來支援早先 6 8 0 0 0家族的微處理器。 P o w e r P C晶片也把浮點運算硬體整合在內部。

          儘管浮點運算硬體對專門從事組合語言程式設計的程式設計師來說是一個很好的禮物,但是,與2 0世紀5 0年代早期開始的其他一些工作相比這只是微不足道的進步。我們的下一個主題是:計算機語言。