一個快速double轉int的方法(利用magic number)


代碼:

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的討論

螺螄殼里做道場,一行代碼有學問……

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM