從printf說開去(三)
(接上回)
我們在C/C++程式碼中使用:
printf(“%f”, 10/3, 0×40080000 );
看到執行結果了嗎?為什麼這行看起來不合乎所謂的語法的printf能輸出3.000000呢?
翻閱手冊,回顧一下printf的格式化引數說明,你會發現%f的型別是double!
我們知道在目前32位的機器上,sizeof(double) = 8位元組 而sizeof(int) = 4位元組,也就是說,機器在處理輸出資料的時候,期望得到一個8位元組的資料空間,而實際上只提供了一個4位元組的資料空間,是不是這裡出了問題?那麼這個過程到底發生了些什麼呢?
10/3 這個資料由於是整數型別常數,他並不是在執行時進行計算的,而是在編譯時,編譯器把他翻譯成了3。實質上,這段程式碼就與寫printf(“%f”, 3)無異,他們完全等價。
既然等價,為什麼使用printf(“%f”, 3.0f);會輸出正確的值?sizeof(3.0f) 不也等於4麼?3.0f 和 整數3 是否等價?3.0和3.0f是否等價?
似乎有點越扯越遠,大家會不會有點糊塗了?要解釋清楚這個過程確實需要一點口舌,不過別急,我們一個問題一個問題地說吧:
問題一:printf(“%f”, 3.0f) 與 printf(“%f”, 3)的區別,3.0f和整數3在儲存上有什麼區別?3.0和3.0f在儲存上有什麼區別?
其實,3.0f和整數3在記憶體中的表示是完全不同的,他們都佔用4個位元組空間,整數3在記憶體當中就是 0×00000003,而3.0f在記憶體中卻為:0×40400000。
並且,你會發現,3.0和3.0f在記憶體中的表示也不相同,3.0是一個雙精度浮點數,他在記憶體中表示為:0×4008000000000000。你可以通過偵錯程式檢視這一點。
那麼浮點數是按什麼規則表示的呢?IEEE標準從邏輯上用三元組{S,E,M}來表示浮點數N,其中S代表符號位,E代表指數位,M代表尾數。
如果是單精度浮點數(float):N共32位,其中S佔1位,E佔8位,M佔23位
如果是雙精度浮點數(double):N共64位,其中S佔1位,E佔11位,M佔52位。
(注:本文並不打算詳細探討浮點數的表示規則,有興趣的朋友可自行參考IEEE754浮點數標準)
N可以用以下公式算得:
N = (-1)^S * m * 2^e
當E的二進位制位不全為0,也不全為1時,
e = |E| – bias (bias = 2^(k-1) – 1)
單精度時k=8,bias=127 雙精度時k=11,bias=1023
其中m = |1.M|
當E的二進位制位全部為0時,此時:
e = 1- bias
m = |0.M|
當E的二進位制位全為1時,若M的二進位制位全為0,則n表示無窮大,若S為1則表示負無窮,S為0則為正無窮。若M的二進位制位不全為0時,表示NaN(Not a Number),代表著不合法或未初始化的值。
例如:
單精度浮點數3.0f,表示為2進位制:
S | E | M |
0 10000000 10000000000000000000000
N = (-1)^0 * 1.5 * 2^1 = 3.0f
雙精度浮點數3.0,表示為2進位制:
符號位 指數位 尾數位
S | E | M |
0 01000000000 1000 0000 0000 0000 … 0000
看到了嗎?他們的計算原理是一樣的,但是位數不同,導致他們在記憶體裡面的表示也不相同。
3 在記憶體裡面用16進製表示為:0×00000003
3.0f 在記憶體裡面用16進製表示為:0×40400000
3.0 在記憶體裡面用16進製表示為:0×4008000000000000
我們現在知道了printf中%f是按double進行處理的,那麼按雙精度浮點規則,我們來看3是怎麼被算成0的?
3 用 64bit二進位制表示:
0000 0000 0000 0000 0000 0000 0000 … 0000 0011
S | E | M |
0 00000000000 0000 0000 0000 0000 … 0011
這裡的m已經是一個非常小,近乎於0的數字了,因此,在float保有的精度範圍內,顯示成為了0。
有興趣的同學可以算算,這個值大約等於:1.48乘以負的323次方。
好,現在弄清楚了浮點數的表示,新的問題又來了,printf(“%f”, 10/3, 0×40080000 ); 能顯示3.000000,這又是怎麼工作的呢?
(未完待續)