简体   繁体   English

打包和解包浮点数时,如何消除浮点不准确?

[英]How can I eliminate floating point inaccuracy when I pack and unpack a floating point number?

I am packing an array of numbers to send via UDP to another piece of hardware using socket programming. 我正在打包一个数字数组,通过UDP使用套接字编程发送到另一个硬件。

When I pack the number 12.2 and then unpack it, I get 12.199999892651. 当我pack 12.2然后unpack它时,我得到12.199999892651。 As I am working with numbers related to latitudes and longitudes, I cannot have such deviations. 当我处理与纬度和经度相关的数字时,我不能有这样的偏差。

This is the simple script I wrote: 这是我写的简单脚本:

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";

The output is: 输出是:

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

Is there any way I can control the precision? 有什么方法可以控制精度吗?

pack 's f template is for single-precision floating point numbers, which on most platforms is good to 7 or so decimal places of accuracy. packf模板用于单精度浮点数,在大多数平台上,精度为7位左右。 The d template offers double-precision and will be good enough for ~15 decimal places. 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"

The short answer is: don't pack these numbers as floats. 简短的回答是:不要将这些数字打包为浮点数。 You will lose accuracy due to IEEE floating point representation. 由于IEEE浮点表示,您将失去准确性。 Instead, convert them to "character decimals" (ie strings), and pack them as strings. 而是将它们转换为“字符小数”(即字符串),并将它们打包为字符串。 If you really need the accuracy, and don't need to perform math operations on them, you may want to store them as strings in Perl as well. 如果您确实需要准确性,并且不需要对它们执行数学运算,您可能还希望将它们存储为Perl中的字符串。

2/10 is a periodic number in binary just like 1/3 is a periodic number in decimal. 2/10是二进制的周期数,就像1/3是十进制的周期数。 It's impossible to store it exactly in a floating point number as it would take infinite storage. 将它精确地存储在浮点数中是不可能的,因为它需要无限存储。

As such, it's not pack that's introducing the error; 因此,引入错误的不是pack ; it's faithfully storing precisely the number you provided it. 它忠实地存储您提供的数字。

$ perl -E'say sprintf "%.20e", 12.2'
1.21999999999999992895e+01

$ perl -E'say sprintf "%.20e", unpack "d", pack "d", 12.2'
1.21999999999999992895e+01

As long as you use floating point numbers, you will not be able to store 12.2 exactly. 只要您使用浮点数,就无法准确存储12.2。

But as you can see above, you can store store precisely enough by using d (double-precision, almost 16 digits of precision) instead of f (single-precision, over 7 digits of precision). 但正如您在上面所看到的,您可以使用d (双精度,几乎16位精度)而不是f (单精度,超过7位精度)来精确存储存储。 Perl uses double-precision, so you were actually introducing precision loss by using f instead of d . Perl使用双精度,所以你实际上通过使用f而不是d来引入精度损失。

So use d , and round your results ( sprintf "%.10f" ). 所以使用d ,并对结果进行舍入( sprintf "%.10f" )。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM