Perl 当我打包和解包浮点数时,如何消除浮点数的不精确性?
我正在打包一个数字数组,以便使用套接字编程通过UDP发送到另一个硬件 当我Perl 当我打包和解包浮点数时,如何消除浮点数的不精确性?,perl,pack,unpack,Perl,Pack,Unpack,我正在打包一个数字数组,以便使用套接字编程通过UDP发送到另一个硬件 当我pack12.2这个数字,然后unpack12.199999892651。因为我在处理与纬度和经度有关的数字,所以我不能有这样的偏差 这是我写的简单脚本: use warnings; use Time::HiRes qw (sleep); @Data = ( 20.2, 30.23, 40.121, 1, 2, 3, 4, 6. 4, 3.2, 9.9, 0.1, 12.2, 0.99, 7.8, 999, 12.3
pack
12.2这个数字,然后unpack
12.199999892651。因为我在处理与纬度和经度有关的数字,所以我不能有这样的偏差
这是我写的简单脚本:
use warnings;
use Time::HiRes qw (sleep);
@Data = ( 20.2, 30.23, 40.121, 1, 2, 3, 4, 6. 4, 3.2, 9.9, 0.1, 12.2, 0.99, 7.8, 999, 12.3 );
$myArr = pack('f*', @Data);
print "$myArr\n\n";
@Dec = unpack('f*',$myArr);
print "@Dec";
输出为:
20.2000007629395 30.2299995422363 40.1209983825684 1 2 3 4 6.40000009536743 3.20 000004768372 9.89999961853027 0.100000001490116 12.1999998092651 0.9900000095367 43 7.80000019073486 999 12.3000001907349
有什么方法可以控制精度吗?简单的回答是:不要将这些数字打包为浮点数。由于IEEE浮点表示,您将失去准确性。相反,将它们转换为“字符小数”(即字符串),并将它们打包为字符串。如果您确实需要精确性,并且不需要对它们执行数学运算,那么您可能还希望将它们作为字符串存储在Perl中
pack
的f
模板适用于单精度浮点数,在大多数平台上,精度高达7位左右。d
模板提供了双精度,并且足够精确到15位小数
print unpack("f", pack("f",12.2)); # "12.1999998092651"
print unpack("d", pack("d",12.2)); # "12.2"
printf "%.20f",unpack("f", pack("f",12.2)); # "12.19999980926513671875"
printf "%.20f",unpack("d", pack("d",12.2)); # "12.19999999999999928946"
2/10是二进制的周期数,就像1/3是十进制的周期数一样。它不可能精确地存储在一个浮点数中,因为它需要无限的存储空间 因此,引入错误的不是
pack
;它准确地存储了您提供的号码
$ perl -E'say sprintf "%.20e", 12.2'
1.21999999999999992895e+01
$ perl -E'say sprintf "%.20e", unpack "d", pack "d", 12.2'
1.21999999999999992895e+01
只要使用浮点数,就无法准确存储12.2
但正如您在上面看到的,您可以使用d
(双精度,几乎16位精度)而不是f
(单精度,超过7位精度)来足够精确地存储数据。Perl使用双精度,因此实际上是通过使用f
而不是d
引入精度损失
因此,请使用
d
,并对结果进行四舍五入(sprintf“%.10f”
)。我认为你应该将第二个问题分成一个单独的帖子;这与第一个问题没有太大关系。未来的访问者将更容易通过这种方式找到答案。12.2是一个十进制数,等于122/10。但是它不能用表达式m/(2**n)
表示,因此不能使用依赖于C的浮点类型的数据类型准确表示。当代码遇到数字12.2时,精度就丢失了——在内部,没有浮点值12.2这样的东西。我们在10号基地也有类似的问题,但已经习惯了,所以我们并不感到惊讶。例如,用十进制表示1/3:0.33?通过某些度量接近,但不准确。您的数组@Data
是人为的。通常它由文本字符串组成,而不是浮点数。我认为您需要发送文本“@Data”
,而不是尝试将值打包为浮点值。“另一端”,不管它是什么,都可以轻松地在空白处分割字符串,并准确地恢复发送的内容。请始终使用strict
,即使是在最琐碎的Perl程序中,假设您处理的是地球上以度为单位的纬度和经度,然后是12.2和12.199999892651之间的错误,赤道处为12mm。接收硬件端只能接受值。。。它不能接受这些细线,因此我认为你将不得不接受一些准确性的损失。您可以尝试使用“d”(双精度)包格式而不是“f”。@Asheesh:如果接收端只能接受压缩浮点,那么您将陷入精度下降的困境。@Borodin Jinx.:)