浮點運算中的正規化
在一般科學記號裡,我們總是在小數點前寫一個非零數字:我們寫 3.0 乘 10^2,而非 0.3 乘 10^3 或 30 乘 10^1,儘管三者相等。堅持這一種標準形式就是正規化。浮點在二進位裡做同樣的事:正規化的數在二進位小數點前剛好有一個 1,寫成 1.fff... 乘 2^e。讓每個數保持這個標準樣貌,意味著每個值只有單一表示,而且每個可用的有效位數位元都承載真正的精度,而不是浪費在前導零上。
一次算術步驟後,結果通常不是正規化的,於是 FPU 修正它。兩種情形。若相加兩個相近的數抵銷了高位——比方結果出來是 0.0011 乘 2^e——硬體把有效位數左移到領頭的 1 抵達標準位置,並把指數減去移位的次數(這裡左移 3,所以指數變 e 減 3)。若一次乘法或加法在領頭 1 之外產生了進位——比方 11.01 乘 2^e——它右移一位、指數加一。「總是有個領頭 1」帶來一個漂亮的附帶好處:既然它一定在,IEEE-754 就不儲存它:那是「隱藏」或隱含位元,免費換來多一位元的精度。
正規化正是浮點兩個危害現身之處。劇烈抵銷——相減兩個幾乎相等的數——可能只剩幾個有意義的位元,接著被往上移位並補位,於是答案看似精確、實則大半是雜訊(有效位數流失)。而若正規化把指數推到最小可表示值以下,這個數就向零下溢;IEEE-754 用次正規數做漸進下溢來緩和這道斷崖,次正規數並非正規化,以犧牲精度換取表示極小量值。
相加有效位數得到 0.00101 x 2^4。正規化時左移 3,讓領頭的 1 落在小數點前:1.01 x 2^1(指數 4 減 3)。若改為某個和溢成 10.1 x 2^4,則右移 1 得 1.01 x 2^5。
移位有效位數並調整指數,直到二進位小數點前剛好有一個 1。
相減幾乎相等的數(災難性抵銷)只剩很少真實位元;正規化接著移入零,所以結果看似精確、實則大半是雜訊。解法在於你怎麼寫算式,而非硬體。