網友ktissot在用下面3條指令將浮點數轉換為雙整數時,發現了一個神秘的現象。
L MD0
RND //浮點數四舍五入
T MD4
我們知道,最大的正的雙整數是2147483647,大于這個數,RND指令轉換會出錯。
但是他的帖子說實際上能轉換的最大的浮點數不是2147483647.0,而是2147483583.0。
這一段本應正常轉換的區間轉換會出錯,為什么轉換出錯的分界點是2147483583.0,這里面隱藏了什么秘密?
這件事引起我極大的興趣,為此我用仿真做了大量的實驗,下面是我做實驗發現的更多的現象:
1.轉換出錯的情況
RND指令的幫助中說:“如果超出允許范圍,則狀態位OV和OS被置位為1。結果存在累加器1中"?!俺霈F錯誤(使用了不能表示為32位整數的NaN或浮點數)時不執行轉換并顯示溢出。"。
下圖用程序狀態監控狀態字STATUS WORD。
大于2147483647.0時轉換出錯。
大于2147483584.0到2147483647.0這一段照理說在應該在能轉換的范圍里,但是轉換也會出錯,狀態字的OV和OS位(第4、5位)被置位為1(見下圖)。MD4中是沒有轉換的浮點數,而不是雙整數。轉換出錯的分界點是2147483584.0。
2.轉換成功的情況
小于16777215.0左右轉換結果正確。轉換成功時OV和OS為0。
小于2147483584.0到16777215.0這一段可以轉換,但是轉換有誤差。
接近2147483584.0時,最大誤差為64。
大于2147483456.0 ~ 2147483584.0時(區間范圍為128.0),轉換后得到的雙整數均為2147483520(16#7FFF FF80,見下圖)。轉換結果2147483520是區間中點的值,(2147483456 + 2147483584)/2= 2147483520。
大于2147483328.0 ~ 2147483456.0時,轉換后得到的雙整數均為2147483392(16#7FFF FF00)。
大于2147483200.0 ~ 2147483328.0時,轉換后得到的雙整數均為2147483264(16#7FFF FE80)。
經過分析,我已經找到了上述現象的原因,結果暫布,希望有興趣的網友一起來分析一下,共享解決問題的快樂。
3.轉換結果分析
大于2147483456.0 ~ 2147483584.0時轉換后得到2147483520(16#7FFF FF80)。
大于2147483328.0 ~ 2147483456.0時轉換后得到2147483392(16#7FFF FF00)。
大于2147483200.0 ~ 2147483328.0時轉換后得到2147483264(16#7FFF FE80)。
上述3個區間內部的間隔為128.0,轉換結果為區間的中點(2147483456 + 2147483584)/2= 2147483520。
所以這種轉換并不精確,最大誤差為128/2=64。
浮點數轉換為雙整數的轉換誤差的根本原因是32位浮點數和32位雙整數的有效位數的差異造成的。
浮點數由一位符號位、8位指數和尾數的小數部分(23位)組成。尾數的位數決定了浮點數的精度。尾數的整數部分為1,小數部分為23位,所以尾數的有效數字為24位。
雙整數除去一位符號位,其有效位數為31位,因此浮點數的有效位數比雙整數少7位。
2147483456.0 ~ 2147483584.0相差128.0,它們對應的整數為31位有效數字,這些浮點數輸入plc后,因為浮點數的有效位數只有24位,它們的尾數相同,對應的十六進制表示的浮點數均為16#4EFF FFFF,或2.17484e+009(注意有效尾數為十進制7位,而不是對應的整數的10位)。所以轉換為雙整數后均為2147483520。
ktissot網友說:“這就像看一片湖水一樣,你可以看到水和浪花,但是絕對看不到水分子。"由于有效位數較小,浮點數不能分辨“水分子"2147483457.0 ~ 2147483584.0,我們只能看到“浪花"2147483520(16#7FFF FF80)。
如果浮點數較小,例如小于16777215(16#FF FFFF),整數部分只有24位,轉換后就沒有上述的誤差了。在此基礎上增大,整數部分的位數越大,誤差越大。
因為有效位數相差7位,在接近雙整數最大值的區段,浮點數的尾數相差一個數時,轉換為雙整數后,相差128。2的7次方等于128。
4.轉換出錯的原因分析
為什么大于2147483584.0的數不能正確地轉換呢?請注意小于2147483648.0到大于2147483584.0這段范圍剛好是64.0(128.0的一半)。RND指令在轉換時將這段范圍的尾數四舍五入后,尾數的位加1,相當于轉換后的整數加128(16#80),由上述的16#7FFF FF80(2147483520)加16#80后變為16#8000 0000,超出了雙整數整數的允許范圍,產生了溢出,所以轉換出錯。