[分享] 32bits floating-point 比較(等於)的問題
問題情境:
最近在寫 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
07/12 14:55, 3F
推
07/16 10:28, , 4F
07/16 10:28, 4F
→
07/16 10:28, , 5F
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
Perl 近期熱門文章
PTT數位生活區 即時熱門文章