[分享] 32bits floating-point 比較(等於)的問題

看板Perl作者時間8年前 (2016/07/11 17:08), 8年前編輯推噓4(403)
留言7則, 4人參與, 最新討論串1/1
問題情境: 最近在寫 MODBUS Communication (用 ActivePerl v5.16.3 for Windows) 主要是接收 32bits register value 然後轉成「浮點數」 其中手冊裡面有一段 Note:「 If the register value is 3.0+E38, which value represents communication error. 」 於是我用以下寫法來判斷: ############################ # read 32bits register value # # 這裡假設讀到的值為: 0x7F61B1E6(hexadecimal as 3.0E+38) $v = 0x7F61B1E6; ####### # error if(unpack('f',pack('V',$v)) == 3.0E+38) { ... } 發現永遠不會跑到 error 的區塊裡面 查到網頁: http://www-cgi.cs.cmu.edu/afs/cs/user/rgs/mosaic/pl-exp-conv.html 裡面有重要的一段訊息:「 Real numbers (floats and doubles) are in the native machine format only; due to the multiplicity of floating formats around, and the lack of a standard "network" representation, no facility for interchange has been made. This means that packed floating point data written on one machine may not be readable on another - even if both use IEEE floating point arithmetic (as the endian-ness of the memory representation is not part of the IEEE spec). Note that perl uses doubles internally for all numeric calculation, and converting from double -> float -> double will lose precision (i.e. unpack("f", pack("f", $foo)) will not in general equal $foo). 」 於是我瞭解直接用 == operator 來比較是行不通的 i.e. if(unpack("f", pack("V", $v)) == 3.0E+38) { ... } does not work 又查到網頁: http://perldoc.perl.org/perlop.html 裡面有提供一個比較 robust 的方法: sub fp_equal { my ($X, $Y, $POINTS) = @_; my ($tX, $tY); $tX = sprintf("%.${POINTS}g", $X); $tY = sprintf("%.${POINTS}g", $Y); return $tX eq $tY; } Ex. $float = unpack("f", pack("V", $v)); if(fp_equal($float,3.0E+38,8)) { ... } 其中 POINTS 代 8 進去 是我看 http://babbage.cs.qc.cuny.edu/IEEE-754.old/Decimal.html 它 32bits floating-point 的 decimal value of the significand 都是 8 位 這樣的寫法的確可以正確比較 32bits floaint-point 是否等於 3.0E+38 了 但如果我今天要比較的常數值不是 3.0E+38 而是其它的值 這樣的寫法依舊可以 work 嗎?? 於是我故意找了一個反例,要比較的常數值改成 1.06 結果會是如何: ##################### # hexadecimal as 1.06 $v = 0x3F87AE14; $float = unpack("f", pack("V", $v)); ######################### # $tX will be "1.0599999" $tX = sprintf("%.8g", $float); #################### # $tY will be "1.06" $tY = sprintf("%.8g", 1.06); 顯然 "1.0599999"(字串) 不等於 "1.06"(字串) 所以「指定精確度」後再比較,在某些 case 下還是會發生問題 我就在想還有什麼方法可以更 robust 於是我又想到: re-pack 後再比較呢 -------以下實驗------- 程式網址: http://codepad.org/3UTfradi 從 Ouput 可看出 re-pack 後再比較好像更 robust ?? 不知道大家有什麼想法 程式碼: ################################################## # 以下舉例 32bits floating-point 比較(等於) 的問題 $float1 = 3.0e+38; $float2 = 1.06; ####################################################################### # pack 成 32bits floating-point 後再 unpack(i.e. double->float->double) # # ref. http://www-cgi.cs.cmu.edu/afs/cs/user/rgs/mosaic/pl-exp-conv.html $new_float1 = unpack('f',pack('f',$float1)); $new_float2 = unpack('f',pack('f',$float2)); ################################# # 直接用 == operator 比較是否等於 &fp_equal_method1($float1,$new_float1); &fp_equal_method1($float2,$new_float2); ####################################### # 指定精確度(significant)後比較是否等於 &fp_equal_method2($float1,$new_float1); &fp_equal_method2($float2,$new_float2); ############################## # 再重新 pack 後再比較是否等於 &fp_equal_method3($float1,$new_float1); &fp_equal_method3($float2,$new_float2); #################### # 直接用 == operator sub fp_equal_method1{ my $v1 = shift; my $v2 = shift; if($v1 == $v2){ print "fp_equal_method1: $v1 equal $v2\n"; } else{ print "fp_equal_method1: $v1 not equal $v2\n"; } } ############## # Knuth method # # ref. http://perldoc.perl.org/perlop.html (Floating-point Arithmetic) sub fp_equal_method2{ my $v1 = shift; my $v2 = shift; if(sprintf("%.8g",$v1) eq sprintf("%.8g",$v2)){ print "fp_equal_method2: $v1 equal $v2\n"; } else{ print "fp_equal_method2: $v1 not equal $v2\n"; } } ###################### # re-pack then compare sub fp_equal_method3{ my $v1 = shift; my $v2 = shift; if(pack('f',$v1) eq pack('f',$v2)){ print "fp_equal_method3: $v1 equal $v2\n"; } else{ print "fp_equal_method3: $v1 not equal $v2\n"; } } ※ 編輯: cutekid (210.61.233.210), 07/11/2016 17:21:45

07/11 17:43, , 1F
有分享有推
07/11 17:43, 1F

07/11 18:07, , 2F
喔喔~解決了嗎 推分享
07/11 18:07, 2F
※ 編輯: cutekid (61.221.80.36), 07/12/2016 14:53:18

07/12 14:55, , 3F
flu 大,我也不知道到底有沒有真的解決了。內文有更新
07/12 14:55, 3F

07/16 10:28, , 4F
我有個困惑為什麼一定得 unpack 再判斷 XD
07/16 10:28, 4F

07/16 10:28, , 5F
不能直接判斷變數的值是否等於 0x7F61B1E6 就好了嗎 @@
07/16 10:28, 5F

07/16 10:30, , 6F
當然我講的不是一般比較浮點數的方法,是針對原先問題
07/16 10:30, 6F
哈哈,的確可以直接判斷是否等於 0x7F61B1E6 只是手冊上是這樣寫:「 If the register value is 3.0+E38, which value represents communication 」, 而不是寫:「 If the register value is 0x7F61B1E6, which value represents communication 」, 所以我刻意讓自己去碰觸這樣的問題 可以手冊寫是是什麼浮點數,程式碼就照寫 不用刻意將程式碼用 32bits hexadecimal 來比較 ※ 編輯: cutekid (61.221.80.36), 07/18/2016 14:17:42

07/18 19:28, , 7F
原來如此 :)
07/18 19:28, 7F
文章代碼(AID): #1NWs8hrB (Perl)
文章代碼(AID): #1NWs8hrB (Perl)