C語言中int到float的強制型別轉換
最近在看一本名為的書。由於我所看過的計算機理論方面的書較少,加上自己大學期間一直也不用功,所以對於計算機的工作原理以及程式的工作方式我始終只知甚少,印象也十分模糊。
不過,應該說我碰到了一本好書。至少,通過昨晚對浮點數一章的閱讀(呃...我的確之前對浮點數從沒弄明白過),我終於瞭解了C語言中為什麼32位int型資料強制轉換到float型會出現精度不能完全保留的現象:
首先來看看我們可愛的int型變數吧,在一臺典型的32位機器上一個有符號的int型的取值範圍為-2147483648 ~ 2147483647(-2^31 ~ (2^31-1))(注1)。也就是說,在一個4位元組(32位2進位制),除去首位用於符號位表示正負外,其餘的31位都是數字的有效位。
下面再來看看“萬惡的”float型變數:根據IEEE的浮點標準,一個浮點數應該用下述形式來表示:
V=(-1)^s * M * 2^E (公式1)
1、規格化值。當指數域的8個二進位制數字既非全零又非全1時,float數值就是這種情況。設指數域的八位二進位制所表示的十進位制數為e, 則公式1中的E就是 E = e - (2^7 - 1) (公式2);
而且此時,將小數域所表示的二進位制假設為(f22)(f21)...(f1)(f0) (注2) ,則該小數域所表示的值即為f = 0.(f22)(f21)...(f1)(f0).於是M = 1 + f
2. 非規格化值
3. 特殊值。當指數域的8個二進位制數字為全1時即為這種情況。當小數域為全零時,該float值根據符號位的不同表示正無窮或者負無窮;當小數域為非全零時,該float值為NaN(Not a Number)。
以上,只是在C語言中對int和float的規約。具體在程式碼中執行強制型別轉化究竟會發生什麼?從下面兩句很簡單的語句開始:
float b = (float)a;
那麼在記憶體中a和b究竟存放的是什麼值呢?
將a展開為二進位制,其值為0000 0000 0011 0101 0100 0011 0010 0001,其十六進位制即為0x00354321。 因為要轉化為float型,所以首先要對上述二進位制的表示形式改變為 M * 2^E 的形式.由於該數明顯大於1,所以按照IEEE的標準,其浮點形勢必然為規格化值。因此 ,轉化後的形式為
a = 1.101010100001100100001 * 2^21
根據 規格化值的定義,M = 1 + f. 所以f = 0.101010100001100100001.因為float型變數的小數域一共23位。所以b的最後23位可以得出,其值為10101010000110010000100
下面再演繹指數域的值:因為a的指數表示法中,指數E = 21。根據公式2,e = E + (2^7 -1) = 148.所以可以得出b的指數域的二進位制表示為:10010100。在加上原數為正,所以符號位s=0。
所以,可以得出b的二進位制表示為0 10010100 10101010000110010000100。轉化為十六位進位制則是0x4A550C84。換句話說,它儲存在記憶體中的值是與a是完全不同的。但是其間還是有關聯性的——a的首位為1的數值位後的二進位制表示是與b的小數域完全相同的。
很快,問題就出現了。int型的有效位數是31,而float型小數域的有效位只有23位,也就是說如果上面的a的二進位制的有效位超過了24位,那麼float型的小數域的精度就不夠了。因此必須進行舍入。比如:如果上面的a的二進位制為0000 0001 1111 0101 0100 0011 0010 0001。這時b的小數域必須有24位才夠,但是,這顯然是不現實的,因此必須舍入到23位,舍入的原則是:所得結果的最低有效位為0。因此這個a在轉換到float時,其精度就會丟失,因為該float的最後23位變成了11110101010000110010000——這顯然是與原值不符的。
實際上,C語言中對於double型在32位機器上的小數域有52位,對於int型的31位有效位是綽綽有餘了。這就是為什麼大部分C語言教材上鼓勵讀者在執行強制型別轉換時將int型轉換成double。同時,這可能也是為什麼int型能夠直接隱式轉換到double型的緣故。
注1:x ^ y表示 x的y次方
注2:(fn)取0或1
http://seapalace.blog.sohu.com/1586858.html
對文中關於e的描述不是很理解,如上文標紅色部分。我按照上述描述可以做到整數2,3的手動轉換到浮點數,就是不能對1做手動轉換。後來又找到了維基上的描述:
http://zh.wikipedia.org/wiki/IEEE_754
貼出部分:
整體呈現[編輯]
IEEE 754浮點數的三個域二進位制浮點數是以符號數值表示法的格式儲存——最高有效位被指定為符號位(sign bit);“指數部份”,即次高有效的e個位元,儲存指數部分;最後剩下的f個低有效位的位元,儲存“尾數”(significand)的小數部份(在非規約形式下整數部份預設為0,其他情況下一律預設為1)。
指數偏移值[編輯]
指數偏移值(exponent bias),是指浮點數表示法中的指數域的編碼值為指數的實際值加上某個固定的值,IEEE 754標準規定該固定值為2e-1 - 1[2],其中的e為儲存指數的位元的長度。
以單精度浮點數為例,它的指數域是8個位元,固定偏移值是28-1 - 1 = 128−1 = 127.單精度浮點數的指數部分實際取值是從128到-127。例如指數實際值為1710,在單精度浮點數中的指數域編碼值為14410,即14410 = 1710 + 12710.
採用指數的實際值加上固定的偏移值的辦法表示浮點數的指數,好處是可以用長度為e個位元的無符號整數來表示所有的指數取值,這使得兩個浮點數的指數大小的比較更為容易。
規約形式的浮點數[編輯]
如果浮點數中指數部分的編碼值在0 < exponent < 2e-1之間,且尾數部分最高有效位(即整數字)是1,那麼這個浮點數將被稱為規約形式的浮點數。
非規約形式的浮點數[編輯]
如果浮點數的指數部分的編碼值是0,尾數為非零,那麼這個浮點數將被稱為非規約形式的浮點數。IEEE 754標準規定:非規約形式的浮點數的指數偏移值比規約形式的浮點數的指數偏移值大1.例如,最小的規約形式的單精度浮點數的指數部分編碼值為1,指數的實際值為-126;而非規約的單精度浮點數的指數域編碼值為0,對應的指數實際值也是-126而不是-127。實際上非規約形式的浮點數仍然是有效可以使用的,只是它們的絕對值已經小於所有的規約浮點數的絕對值;即所有的非規約浮點數比規約浮點數更接近0。規約浮點數的尾數大於等於1且小於2,而非規約浮點數的尾數小於1且大於0.
IEEE 754-1985標準採用非規約浮點數,源於70年代末IEEE浮點數標準化專業技術委員會醞釀浮點數二進位制標準時,Intel公司對漸進式下溢位(gradual underflow)的力薦。當時十分流行的DECVAX機的浮點數表示採用了突然式下溢位(abrupt underflow)。如果沒有漸進式下溢位,那麼0與絕對值最小的浮點數之間的距離(gap)將大於相鄰的小浮點數之間的距離。例如單精度浮點數的絕對值最小的規約浮點數是,它與絕對值次小的規約浮點數之間的距離為。如果不採用漸進式下溢位,那麼絕對值最小的規約浮點數與0的距離是相鄰的小浮點數之間距離的倍!可以說是非常突然的下溢位到0。這種情況的一種糟糕後果是:兩個不等的小浮點數X與Y相減,結果將是0.訓練有素的數值分析人員可能會適應這種限制情況,但對於普通的程式設計師就很容易陷入錯誤了。採用了漸進式下溢位後將不會出現這種情況。例如對於單精度浮點數,指數部分實際最小值是(-126),對應的尾數部分從, 一直到, ,相鄰兩小浮點數之間的距離(gap)都是;而與0最近的浮點數(即最小的非規約數)也是。
特殊值[編輯]
這裡有三個特殊值需要指出:
- 如果指數是0並且尾數的小數部分是0,這個數±0(和符號位相關)
- 如果指數 = 並且尾數的小數部分是0,這個數是±∞(同樣和符號位相關)
- 如果指數 = 並且尾數的小數部分非0,這個數表示為不是一個數(NaN)。
以上規則,總結如下:
形式 | 指數 | 小數部分 |
---|---|---|
零 | 0 | 0 |
非規約形式 | 0 | 非0 |
規約形式 | 到 | 任意 |
無窮 | 0 | |
NaN | 非零 |
32位單精度[編輯]
單精度二進位制小數,使用32個位元儲存。
1 | 8 | 23 位長 |
S | Exp | Fraction |
31 | 30至23 偏正值(實際的指數大小+127) |
22至0 位編號(從右邊開始為0) |
S為符號位,Exp為指數字,Fraction為有效數字。 指數部分即使用所謂的偏正值形式表示,偏正值為實際的指數大小與一個固定值(32位的情況是127)的和。採用這種方式表示的目的是簡化比較。因為,指數的值可能為正也可能為負,如果採用補碼錶示的話,全體符號位S和Exp自身的符號位將導致不能簡單的進行大小比較。正因為如此,指數部分通常採用一個無符號的正數值儲存。單精度的指數部分是−126~+127加上偏移值127,指數值的大小從1~254(0和255是特殊值)。浮點小數計算時,指數值減去偏正值將是實際的指數大小。
單精度浮點數各種極值情況:
類別 | 正負號 | 實際指數 | 有偏移指數 | 指數域 | 尾數域 | 數值 |
---|---|---|---|---|---|---|
零 | 0 | -127 | 0 | 0000 0000 | 000 0000 0000 0000 0000 0000 | 0.0 |
負零 | 1 | -127 | 0 | 0000 0000 | 000 0000 0000 0000 0000 0000 | −0.0 |
1 | 0 | 0 | 127 | 0111 1111 | 000 0000 0000 0000 0000 0000 | 1.0 |
-1 | 1 | 0 | 127 | 0111 1111 | 000 0000 0000 0000 0000 0000 | −1.0 |
最小的非規約數 | * | -126 | 0 | 0000 0000 | 000 0000 0000 0000 0000 0001 | ±2−23 × 2−126 = ±2−149 ≈ ±1.4×10-45 |
中間大小的非規約數 | * | -126 | 0 | 0000 0000 | 100 0000 0000 0000 0000 0000 | ±2−1 × 2−126 = ±2−127 ≈ ±5.88×10-39 |
最大的非規約數 | * | -126 | 0 | 0000 0000 | 111 1111 1111 1111 1111 1111 | ±(1−2−23) × 2−126 ≈ ±1.18×10-38 |
最小的規約數 | * | -126 | 1 | 0000 0001 | 000 0000 0000 0000 0000 0000 | ±2−126 ≈ ±1.18×10-38 |
最大的規約數 | * | 127 | 254 | 1111 1110 | 111 1111 1111 1111 1111 1111 | ±(2−2−23) × 2127 ≈ ±3.4×1038 |
正無窮 | 0 | 128 | 255 | 1111 1111 | 000 0000 0000 0000 0000 0000 | +∞ |
負無窮 | 1 | 128 | 255 | 1111 1111 | 000 0000 0000 0000 0000 0000 | −∞ |
NaN | * | 128 | 255 | 1111 1111 | non zero | NaN |
* 符號位可以為0或1 . |