当我打包和解包浮点数时,如何消除浮点不准确性?
Posted
技术标签:
【中文标题】当我打包和解包浮点数时,如何消除浮点不准确性?【英文标题】:How can I eliminate floating point inaccuracy when I pack and unpack a floating point number? 【发布时间】:2016-09-01 19:21:44 【问题描述】:我正在打包一组数字,以便使用套接字编程通过 UDP 发送到另一块硬件。
当我 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
有什么方法可以控制精度吗?
【问题讨论】:
我认为您应该将第二个问题拆分为单独的帖子;它与第一个问题并没有真正的关系。这样以后的访问者会更容易找到答案。 12.2 是一个十进制数,等于 122/10。但是它不能用表达式m/(2**n)
来表示,因此不能用依赖于C的浮点类型的数据类型来准确表示。当您的代码遇到数字 12.2 时,精度就会丢失——在内部没有浮点值 12.2 这样的东西。我们在 base-10 中也有类似的问题,但已经习惯了它们,所以它们并不让我们感到惊讶。例如,以十进制形式表示 1/3:0.33?在某些方面接近,但不准确。
您的数组@Data
是人造的。通常它将由文本字符串组成,而不是浮点数。我认为您需要发送文本 "@Data"
而不是尝试将值打包为浮点数。 “另一端”,无论它是什么,都可以轻松地将字符串拆分为空格并准确恢复发送的内容。
拜托,总是 use strict
,即使在最琐碎的 Perl 程序中
假设您在地球上以度为单位处理纬度和经度,那么赤道处 12.2 和 12.199999892651 之间的误差为 12mm。
【参考方案1】:
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】:简短的回答是:不要将这些数字打包为浮点数。由于 IEEE 浮点表示,您将失去准确性。相反,将它们转换为“字符小数”(即字符串),并将它们打包为字符串。如果您确实需要准确性,并且不需要对它们执行数学运算,您可能还希望将它们作为字符串存储在 Perl 中。
【讨论】:
接收硬件端只能接受值...不能接受字符串 那么我认为你将不得不接受一些准确性的损失。您可以尝试使用“d”(双)包格式而不是“f”。 @Asheesh:如果接收端只能接受打包的浮点数,那么你就会陷入失去准确性的困境。 @Borodin Jinx。 :)【参考方案3】: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"
)。
【讨论】:
以上是关于当我打包和解包浮点数时,如何消除浮点不准确性?的主要内容,如果未能解决你的问题,请参考以下文章