问题情境:
最近在写 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
1F:推 abliou: 有分享有推 07/11 17:43
2F:推 flu: 喔喔~解决了吗 推分享 07/11 18:07
※ 编辑: cutekid (61.221.80.36), 07/12/2016 14:53:18
3F:→ cutekid: flu 大,我也不知道到底有没有真的解决了。内文有更新 07/12 14:55
4F:推 LiloHuang: 我有个困惑为什麽一定得 unpack 再判断 XD 07/16 10:28
5F:→ LiloHuang: 不能直接判断变数的值是否等於 0x7F61B1E6 就好了吗 @@ 07/16 10:28
6F:→ LiloHuang: 当然我讲的不是一般比较浮点数的方法,是针对原先问题 07/16 10:30
哈哈,的确可以直接判断是否等於 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
7F:推 LiloHuang: 原来如此 :) 07/18 19:28