完整版教程下載地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=94547
第8章 DSP定點數和浮點數(重要)
本期教程主要跟大家講解一下定點數和浮點數的基礎知識,了解這些基礎知識對於后面學習ARM官方的DSP庫大有裨益。特別是初學的一定要理解這些基礎知識。
8.1 定點數和浮點數概念
8.2 IEEE浮點數
8.3 定點數運算
8.4 總結
8.1 初學者重要提示
- 如果之前沒有接觸過這方便的知識點,首次學習會有點不太理解,隨着后面章節的深入就慢慢理解了。
8.2 定點數和浮點數概念
如果小數點的位置事先已有約定,不再改變,此類數稱為“定點數”。相比之下,如果小數點的位置可變,則稱為“浮點數”(定點數的本質是小數,整數只是其表現形式)。
8.2.1 定點數
常用的定點數有兩種表示形式:如果小數點位置約定在最低數值位的后面,則該數只能是定點整數;如果小數點位置約定在最高數值位的前面,則該數只能是定點小數。
8.2.2 浮點數
在計算機系統的發展過程中,曾經提出過多種方法表達實數。典型的比如相對於浮點數的定點數(Fixed Point Number)。在這種表達方式中,小數點固定的位於實數所有數字中間的某個位置。貨幣的表達就可以使用這種方式,比如 99.00 或者 00.99 可以用於表達具有四位精度,小數點后有兩位的貨幣值。由於小數點位置固定,所以可以直接用四位數值來表達相應的數值。SQL 中的 NUMBER 數據類型就是利用定點數來定義的。還有一種提議的表達方式為有理數表達方式,即用兩個整數的比值來表達實數。
定點數表達法的缺點在於其形式過於僵硬,固定的小數點位置決定了固定位數的整數部分和小數部分,不利於同時表達特別大的數或者特別小的數。最終,絕大多數現代的計算機系統采納了所謂的浮點數表達方式。這種表達方式利用科學計數法來表達實數,即用一個尾數(Mantissa ),一個基數(Base),一個指數(Exponent)以及一個表示正負的符號來表達實數。比如 123.45 用十進制科學計數法可以表達為 1.2345 × 102,其中 1.2345 為尾數,10 為基數,2 為指數。浮點數利用指數達到了浮動小數點的效果,從而可以靈活地表達更大范圍的實數。
8.3 IEEE浮點數
說明:Cortex-M7中的FPU(浮點單元)就是用的這個IEEE 754標准,初學的要認真學習。
IEEE二進制浮點數算術標准(IEEE 754)是20世紀80年代以來最廣泛使用的浮點數運算標准,為許多CPU與浮點運算器所采用。這個標准定義了表示浮點數的格式(包括負零-0)與反常值(denormal number),一些特殊數值(無窮Inf)與非數值(NaN),以及這些數值的“浮點數運算符”;它也指明了四種數值舍入規則和五種例外狀況(包括例外發生的時機與處理方式)。
IEEE 754規定了四種表示浮點數值的方式:單精確度(32位)、雙精確度(64位)、延伸單精確度(43比特以上,很少使用)與延伸雙精確度(79比特以上,通常以80比特實現)。只有32位模式有強制要求,其他都是選擇性的。大部分編程語言都有提供IEEE浮點數格式與算術,但有些將其列為非必需的。例如,IEEE 754問世之前就有的C語言,現在有包括IEEE算術,但不算作強制要求(C語言的float通常是指IEEE單精確度,而double是指雙精確度)。
該標准的全稱為IEEE二進制浮點數算術標准(ANSI/IEEE Std 754-1985),又稱IEC 60559:1989,微處理器系統的二進制浮點數算術(本來的編號是IEC 559:1989)。后來還有“與基數無關的浮點數”的“IEEE 854-1987標准”,有規定基數為2跟10的狀況。現在最新標准是“IEEE 854-2008標准”。
在六、七十年代,各家計算機公司的各個型號的計算機,有着千差萬別的浮點數表示,卻沒有一個業界通用的標准。這給數據交換、計算機協同工作造成了極大不便。IEEE的浮點數專業小組於七十年代末期開始醞釀浮點數的標准。在1980年,英特爾公司就推出了單片的8087浮點數協處理器,其浮點數表示法及定義的運算具有足夠的合理性、先進性,被IEEE采用作為浮點數的標准,於1985年發布。而在此前,這一標准的內容已在八十年代初期被各計算機公司廣泛采用,成了事實上的業界工業標准。
在 IEEE 標准中,浮點數是將特定長度的連續字節的所有二進制位分割為特定寬度的符號域,指數域和尾數域三個域,其中保存的值分別用於表示給定二進制浮點數中的符號,指數和尾數。這樣,通過尾數和可以調節的指數(所以稱為"浮點")就可以表達給定的數值了。具體的格式參見下面的圖例:
- 在上面的圖例中,第一個域為符號域。其中 0 表示數值為正數,而 1 則表示負數。
- 第二個域為指數域。其中單精度數為 8 位,雙精度數為 11 位。以單精度數為例,8 位的指數為可以表達 0 到 255 之間的 255 個指數值。但是,指數可以為正數,也可以為負數。
為了處理負指數的情況,實際的指數值按要求需要加上一個偏差(Bias)值作為保存在指數域中的值,單精度數的偏差值為 127,而雙精度數的偏差值為 1023。比如,單精度的實際指數值 0 在指數域中將保存為 127;而保存在指數域中的 64 則表示實際的指數值 -63。 偏差的引入使得對於單精度數,實際可以表達的指數值的范圍就變成 -127 到 128 之間(包含兩端)。我們后面還將看到,實際的指數值 -127(保存為 全 0)以及 +128(保存為全 1)保留用作特殊值的處理。這樣,實際可以表達的有效指數范圍就在 -127 和 127 之間。在本文中,最小指數和最大指數分別用 emin 和 emax 來表達。
- 圖例中的第三個域為尾數域,其中單精度數為 23 位長,雙精度數為 52 位長。除了我們將要講到的某些特殊值外,IEEE 標准要求浮點數必須是規范的。這意味着尾數的小數點左側必須為 1,因此我們在保存尾數的時候,可以省略小數點前面這個 1,從而騰出一個二進制位來保存更多的尾數。這樣我們實際上用 23 位長的尾數域表達了 24 位的尾數。比如:
對於單精度數而言,二進制的 1001.101(對應於十進制的 9.625)可以表達為 1.001101 × 23,所以實際保存在尾數域中的值為 0011 0100 0000 000 0000 0000,即去掉小數點左側的 1,並用 0 在右側補齊。
值得注意的是,對於單精度數,由於我們只有 24 位的指數(其中一位隱藏),所以可以表達的最大指數為 224 - 1 = 16,777,215。特別的,16,777,216 是偶數,所以我們可以通過將它除以 2 並相應地調整指數來保存這個數,這樣 16,777,216 同樣可以被精確的保存。相反,數值 16,777,217 則無法被精確的保存。由此,我們可以看到單精度的浮點數可以表達的十進制數值中,真正有效的數字不高於 8 位。事實上,對相對誤差的數值分析結果顯示有效的精度大約為 7.22 位。參考下面的示例:
根據標准要求,無法精確保存的值必須向最接近的可保存的值進行舍入。這有點像我們熟悉的十進制的四舍五入,即不足一半則舍,一半以上(包括一半)則進。不過對於二進制浮點數而言,還多一條規矩,就是當需要舍入的值剛好是一半時,不是簡單地進,而是在前后兩個等距接近的可保存的值中,取其中最后一位有效數字為零者。從上面的示例中可以看出,奇數都被舍入為偶數,且有舍有進。我們可以將這種舍入誤差理解為"半位"的誤差。所以,為了避免 7.22 對很多人造成的困惑,有些文章經常以 7.5 位來說明單精度浮點數的精度問題。
提示: 這里采用的浮點數舍入規則有時被稱為舍入到偶數(Round to Even)。相比簡單地逢一半則進的舍入規則,舍入到偶數有助於從某些角度減小計算中產生的舍入誤差累積問題。因此為 IEEE 標准所采用。
8.3.1 規范化浮點數
通過前面的介紹,大家應該已經了解的浮點數的基本知識,這些知識對於一個不接觸浮點數應用的人應該足夠了。簡單總結如下:
標准的浮點數都符都符合如下的公式:
其中bias是固定的數值,這個在前面的已經講解過。參數的具體范圍如下
8.3.2 非規范化浮點數
我們來考察浮點數的一個特殊情況。選擇兩個絕對值極小的浮點數,以單精度的二進制浮點數為例,比如 1.001 × 2-125和 1.0001 ×2-125 這兩個數(分別對應於十進制的 2.6448623 × 10-38和 2.4979255 ×10-38)。顯然,他們都是普通的浮點數(指數為 -125,大於允許的最小值 -126;尾數更沒問題),按照 IEEE 754 可以分別保存為 00000001000100000000000000000000(0x1100000)和 00000001000010000000000000000000(0x1080000)。
現在我們看看這兩個浮點數的差值。不難得出,該差值為 0.0001 × 2-125,表達為規范浮點數則為 1.0 × 2-129。問題在於其指數大於允許的最小指數值,所以無法保存為規范浮點數。最終,只能近似為零(Flush to Zero)。這中特殊情況意味着下面本來十分可靠的代碼也可能出現問題:
if (x != y) { z = 1 / (x -y); }
正如我們精心選擇的兩個浮點數展現的問題一樣,即使 x 不等於 y,x 和 y 的差值仍然可能絕對值過小,而近似為零,導致除以 0 的情況發生。
為了解決此類問題,IEEE 標准中引入了非規范(Denormalized)浮點數。規定當浮點數的指數為允許的最小指數值,即 emin 時,尾數不必是規范化的。比如上面例子中的差值可以表達為非規范的浮點數 0.001 × 2-126,其中指數 -126 等於 emin。注意,這里規定的是"不必",這也就意味着"可以"。當浮點數實際的指數為 emin,且指數域也為 emin 時,該浮點數仍是規范的,也就是說,保存時隱含着一個隱藏的尾數位。為了保存非規范浮點數,IEEE 標准采用了類似處理特殊值零時所采用的辦法,即用特殊的指數域值 emin - 1 加以標記,當然,此時的尾數域不能為零。這樣,例子中的差值可以保存為 00000000000100000000000000000000(0x100000),沒有隱含的尾數位。
有了非規范浮點數,去掉了隱含的尾數位的制約,可以保存絕對值更小的浮點數。而且,也由於不再受到隱含尾數域的制約,上述關於極小差值的問題也不存在了,因為所有可以保存的浮點數之間的差值同樣可以保存。
8.3.3 有符號的零
因為 IEEE 標准的浮點數格式中,小數點左側的 1 是隱藏的,而零顯然需要尾數必須是零。所以,零也就無法直接用這種格式表達而只能特殊處理。
實際上,零保存為尾數域為全為 0,指數域為 emin - 1 = -127,也就是說指數域也全為 0。考慮到符號域的作用,所以存在着兩個零,即 +0 和 -0。不同於正負無窮之間是有序的,IEEE 標准規定正負零是相等的。
零有正負之分,的確非常容易讓人困惑。這一點是基於數值分析的多種考慮,經利弊權衡后形成的結果。有符號的零可以避免運算中,特別是涉及無窮的運算中,符號信息的丟失。舉例而言,如果零無符號,則等式 1/(1/x) = x 當x = ±∞ 時不再成立。原因是如果零無符號,1 和正負無窮的比值為同一個零,然后 1 與 0 的比值為正無窮,符號沒有了。解決這個問題,除非無窮也沒有符號。但是無窮的符號表達了上溢發生在數軸的哪一側,這個信息顯然是不能不要的。零有符號也造成了其它問題,比如當 x=y 時,等式1/x = 1/y 在 x 和 y 分別為 +0 和 -0 時,兩端分別為正無窮和負無窮而不再成立。當然,解決這個問題的另一個思路是和無窮一樣,規定零也是有序的。但是,如果零是有序的,則即使 if (x==0) 這樣簡單的判斷也由於 x 可能是 ±0 而變得不確定了。兩害取其輕者,零還是無序的好。
8.3.4 無窮
和 NaN 一樣,特殊值無窮(Infinity)的指數部分同樣為 emax + 1 = 128,不過無窮的尾數域必須為零。無窮用於表達計算中產生的上溢(Overflow)問題。比如兩個極大的數相乘時,盡管兩個操作數本身可以用保存為浮點數,但其結果可能大到無法保存為浮點數,而必須進行舍入。根據 IEEE 標准,此時不是將結果舍入為可以保存的最大的浮點數(因為這個數可能離實際的結果相差太遠而毫無意義),而是將其舍入為無窮。對於負數結果也是如此,只不過此時舍入為負無窮,也就是說符號域為 1 的無窮。有了 NaN 的經驗我們不難理解,特殊值無窮使得計算中發生的上溢錯誤不必以終止運算為結果。
無窮和除 NaN 以外的其它浮點數一樣是有序的,從小到大依次為負無窮,負的有窮非零值,正負零(隨后介紹),正的有窮非零值以及正無窮。除 NaN 以外的任何非零值除以零,結果都將是無窮,而符號則由作為除數的零的符號決定。
當零除以零時得到的結果不是無窮而是 NaN 。原因不難理解,當除數和被除數都逼近於零時,其商可能為任何值,所以 IEEE 標准決定此時用 NaN 作為商比較合適。
8.3.5 NaN
NaN 用於處理計算中出現的錯誤情況,比如 0.0 除以 0.0 或者求負數的平方根。由上面的表中可以看出,對於單精度浮點數,NaN 表示為指數為 emax + 1 = 128(指數域全為 1),且尾數域不等於零的浮點數。IEEE 標准沒有要求具體的尾數域,所以 NaN 實際上不是一個,而是一族。不同的實現可以自由選擇尾數域的值來表達 NaN。
8.4 定點數運算
8.4.1 數的定標(Q格式)
在許多情況下,數學運算過程中的數不一定都是整數,而且定點DSP和不帶FPU的處理器是無能為力的。那么是不是說定點DSP和不帶FPU的處理器就不能處理各種小數呢?當然不是。這其中的關鍵就是由程序員來確定一個數的小數點處於數據中的哪一位。這就是數的定標(由於很多時候,我們都是直接用C來實現浮點運算,具體的底層轉化我們並沒有去關心,所以也就很少有人知道數的定標)。
通過設定小數點在數據中的不同位置,就可以表示不同大小和不同精度的小數了。數的定標有Q表示法和S表示法兩種。下表列出了一個16位數的16種Q表示、S表示及它們所能表示的十進制數值范圍。
從上表可以看出,同樣一個16位數,若小數點設定的位置不同,它所表示的數也就不同。例如,
16進制數2000H=8192,用Q0表示
16進制數2000H=0.25,用Q15表示
還可以看出,不同的Q所表示的數不僅范圍不同,而且精度也不相同。Q越大,數值范圍越小,但精度越高;相反,Q越小,數值范圍越大,但精度就越低。例如,Q0 的數值范圍是-32768到+32767,其精度為1,而Q15的數值范圍為-1到0.9999695,精度為1/32768=0.00003051。因此,對定點數而言,數值范圍與精度是一對矛盾,一個變量要想能夠表示比較大的數值范圍,必須以犧牲精度為代價;而想精度提高,則數的表示范圍就相應地減小。在實際的定點算法中,為了達到最佳的性能,必須充分考慮到這一點。
浮點數與定點數的轉換關系可表示為:
例如,浮點數x=0.5,定標Q=15,則定點數xq=L0.5*32768J=16384,式中LJ表示下取整。反之,一個用Q=15表示的定點數16384,其浮點數為16384 *2^-15=16384/32768=0.5。浮點數轉換為定點數時,為了降低截尾誤差,在取整前可以先加上0.5。
8.4.2 定點數的算術運算
關於定點數的算術運算會在講解ARM官方的DSP教程時專門給大家講解。
8.5 總結
本期教程就跟大家講這么多,這部分知識對於初學DSP的非常重要,建議認真學習下,有興趣的可以在網上多查些資料進行了解。