人逢喜事精神爽,總算熬到下班撩~~
正準備和同僚打個招呼回家,被同僚拖住問了.
🙋♂️: 你們組做的那塊代碼,把double類型資料成float有問題啊💨.
💁♀️: 嗯?不對是正常啊,float精度是沒有double高,但float能儲存到小數點後好多位,對我們來說完全夠用了!
🙋♂️: 不是啊,這不是小數點多少位的問題,而是現在整型資料,轉出來也有問題啊,你看.

💁♀️: XX00😱.... 這什麼鬼?
看到這個結果,差點閃到我的老腰🤦,咋不按套路出牌呢?
然後,下班路上,感覺我好像被我摯愛的.Net欺騙了💔,double強轉float用了這麼多年,咋說不對就不對了?.Net不靠譜啊!
當然,我内心還是相信.Net是清白的,是以刨根究底,網上找的資料大多是說這種強轉會照成小數點後的精度的問題,可是造成整數位的問題精度問題卻少有人提及.
為了了解這個問題,我們要從一些大學計算機基礎的相關知識講起😂.
float四個位元組,double八個位元組.
float範圍從10-38到1038 和 -1038到-10-38, double的範圍從10-308到10308 和 -10-308到-10-308
當然了,這都是廢話🤷, 重點是下面這條.
float是<code>單精度浮點數</code>,double是<code>雙精度浮點數</code>.
根據國際标準IEEE 754,任意一個二進制浮點數V可以表示成下面的形式:
(-1)^s表示符号位,當s=0,V為正數;當s=1,V為負數。
M表示有效數字,大于等于1,小于2。
2^E表示指數位。
舉例來說,十進制的5.0,寫成二進制是101.0,相當于1.01×2^2。那麼,按照上面V的格式,可以得出s=0,M=1.01,E=2。
十進制的-5.0,寫成二進制是-101.0,相當于-1.01×2^2。那麼,s=1,M=1.01,E=2。
對于32位的<code>單精度</code>浮點數,最高的1位是符号位s,接着的8位是指數E,剩下的23位為有效數字M。
對于64位的<code>雙精度</code>浮點數,最高的1位是符号位S,接着的11位是指數E,剩下的52位為有效數字M。
經過上面關于浮點數的介紹,相信你可能還是一頭霧水,就像下面這幅漫畫展示的那樣🐎.
為了避免産生上面那種畫馬的跳躍,我們一小步一小步,看看浮點資料具體怎麼在記憶體中存儲的.雙精度與單精度類似,這裡我以單精度為例.
先将這個實數的絕對值化為二進制格式。
将這個二進制格式實數的小數點左移或右移n位,直到小數點移動到第一個有效數字的右邊。
從小數點右邊第一位開始數出二十三位數字放入第22到第0位。
如果實數是正的,則在第31位放入“0”,否則放入“1”。
⭐如果n 是左移得到的,說明指數是正的,第30位放入“1”。如果n是右移得到的或n=0,則第30位放入“0”。
如果n是左移得到的,則将n減去1後化為二進制,并在左邊加“0”補足七位,放入第29到第23位。如果n是右移得到的或n=0,則将n化為二進制後在左邊加“0”補足七位,再各位求反,再放入第29到第23位。
我們先用上述步驟嘗試把9.0轉化成二進制存儲形式.
我們可以通過這個位址校驗計算結果的正确性. https://www.h-schmidt.net/FloatConverter/IEEE754.html
可以看到,與我們的計算結果完全一緻.
現在我們用上面的步驟,把照成翻車的83459338轉成記憶體存儲形式看看.
通過線上工具轉換後證明我們的轉換完全正确.
然後我們再把資料轉回來.
S是第31位,為0, E =<code>0011001</code>(25)+1=26, 重點在M,它是1.(<code>有效數字位</code>)即 <code>1.00111110010111110100001</code>
<code>1.00111110010111110100001</code>乘上2的26次方,為<code>100111110010111110100001000</code>,将其轉換為十進制,為 <code>83459336</code>
沒錯,就是<code>83459336</code>,而不是<code>83459338</code>🌋
83459338=> <code>100111110010111110100001010</code>
83459336=> <code>100111110010111110100001000</code>
可以看到,兩個數字轉成成二進制後,倒數第二位産生了差異,而産生這種的差異的原因就是單精度浮點數小數位23位不足以存儲所有二進制數(26位).
🚑這場事故告訴我們,強轉雖好,容易翻車.