代碼:
int i = *reinterpret_cast<int*>(&(d += 6755399441055744.0));
知識點:
1.reinterpret_cast<type_id> expression:type_id 必須是一個指針、引用、算術類型、函數指針或者成員指針。它可以把一個指針轉換成一個整數,也可以把一個整數轉換成一個指針(先把一個指針轉換成一個整數,再把該整數轉換成原類型的指針,還可以得到原先的指針值)。
只是將bit表示進行了重新解讀,不改變位表示
reinterpret_cast 只能在指針之間轉換。
更多信息,參見msdn
2.magic number的原理:stackoverflow的頁面
6755399441055744.0 = 1<<51 + 1<<52 = 2,251,799,813,685,248 + 4,503,599,627,370,496
根據double類型的格式可知:尾數52位
從而double類型有一個性質:當數據大小處於2^52~2^53范圍內時,可表示的數恰好為該范圍內的整數(根據數據格式顯然可知)
從而把一個(相對)非常小的double類型數強行規約到2^52~2^53范圍內,截取它的后32位即為相應的int表示(因為加的數實際為11000...000,只影響第52和53位,都已經被舍去)
3.一些問題:
1)為什么選擇1<<51 + 1<<52?
嘗試1<<52作為參數,發現正數范圍內舍入正常,但負數范圍內出現奇怪的結果:
[0~-0.25) --> 0
[-0.25~-0.75) --> -1
[-0.75~-1.25] --> -2
(-1.25~1.75) --> -3
行為類似於數據大小位於2^53~2^54范圍的表現(只表示范圍內的全部偶數,相當於上文范圍*2)
猜想:1<<51是為了讓負數的表示也正常?如何實現?
int中的負數表示利用了補碼,而double中利用了符號位,顯然不同
假設某個負數為(-a),則經過這樣變換后,變為(2^52+2^51-a),顯然為正且在2^52與2^53之間
同時,如果我們用補碼形式看待這個結果,發現2^52+2^51只影響高位,0~50位不受影響。
為什么要+1<<51?由於double類型的尾數前面有一個隱含的前導1,在加法時無影響,但減法時向前借位減就會漏掉這個前導1,因而需要1<<51手動將尾數第一位設為1(反正最后要舍去)
關於double計算的更多細節,參見此處
2)數據范圍
理論上來說double的大小不能大於2^51,但考慮到int的范圍,足夠用了
(注意:此處的溢出行為與通常的轉換例如static_cast<int>不同)
3)存在的問題
1.引用還是指針?
更好的寫法:int &i = reinterpret_cast<int &>(d += 6755399441055744.0);
原因:
int i = *reinterpret_cast<int*>(&(d += 6755399441055744.0)); 計算->取引用->reinterpret_cast改類型->取指針值
int &i = reinterpret_cast<int &>(d += 6755399441055744.0); 計算->改類型,較為明確
要深入理解引用與指針的區別
2.舍入規則:round-to-even,2.5-->2,3.5-->4
原因:double加法運算的原理?
3.會有strict alias的問題,C++中為Undefined Behavior
關於strict alias:當兩個指針指向同一塊區域或對象時,我們稱一個指針 alias 另一個指針
Strict aliasing 是C或C++編譯器的一種假設:不同類型的指針絕對不會指向同一塊內存區域。
更多關於strict alias的討論
螺螄殼里做道場,一行代碼有學問……