Perl可以检测浮点数是否已被隐式舍入?

时间:2016-09-17 19:42:47

标签: perl floating-point

当我使用代码时:

(sub {
    use strict;
    use warnings;

    print 0.49999999999999994;
})->();

Perl输出" 0.5"。

当我删除一个" 9"来自数字:

(sub {
    use strict;
    use warnings;

    print 0.4999999999999994;
})->();

打印0.499999999999999。

只有当我删除另一个9时,它才能准确地存储数字。

我知道浮点数是一个没有人想要处理的蠕虫病毒,但我很好奇Perl中是否存在陷入困境的方法#34;这种隐式转换和死亡,这样我就可以使用eval来捕获这个死亡并让用户知道他们试图传递的号码不被Perl支持。原生形式(因此用户可以改为传递字符串或对象)。

我需要这个的原因是为了避免传递0.49999999999999994被我的函数舍入的情况,但是数字被转换为0.5,然后被舍入为1而不是0.我不知道如何& #34;截距"这种转换使我的功能"知道"它实际上没有得到0.5作为输入,但是用户的输入被截获了。

不知道如何拦截这种转换,我不能相信" round"因为我不知道它是否在我发送它时收到了我的输入,或者在调用函数之前是否已经修改了(在编译时或运行时,不确定)(反过来,函数不知道是否输入它正在进行操作是用户意图与否的输入,并且没有办法警告用户。)

这不是Perl独有的问题,它发生在JavaScript中:

(() => {
    'use strict';

    /* oops: 1 */
    console.log(Math.round(0.49999999999999999))
})();

它发生在Ruby中:

(Proc.new {
    # oops: 1 
    print (0.49999999999999999.round)
}).call()

它发生在PHP中:

<?php
(call_user_func(function() {
    /* oops: 1 */
    echo round(0.49999999999999999);
}));
?>

它甚至发生在C(这是可以发生的,但我的gcc并没有警告我数字没有精确存储(当指定特定的浮点文字时,最好准确存储它们,或者编译器应该警告你决定把它变成另一种形式(例如&#34;你的数字x不能用64位/ 32位浮点形式表示,所以我将它转换为y。&#34;)所以你可以看到它是否& #39;没关系,在这种情况下它不是)):

#include <math.h>
#include <stdio.h>

int main(int argc, char **argv)
{
    /* oops: 1 */
    printf("%f.\n", round(0.49999999999999999));

    return 0;
}

摘要

是否有可能在浮点数的隐式转换中使Perl显示错误或警告,或者这是Perl5(以及其他语言)此时无法执行的操作(例如,编译器不会超出其&# 39;支持此类警告的方式/提供启用此类警告的标志)?

e.g。

  

警告:数字0.49999999999999994无法表示,已被转换为0.5。使用bigint可能会解决这个问题。考虑降低数字的精确度。

3 个答案:

答案 0 :(得分:6)

也许使用BigNum

$ perl -Mbignum -le 'print 0.49999999999999994'
0.49999999999999994
$ perl -Mbignum -le 'print 0.49999999999999994+0.1'
0.59999999999999994
$ perl -Mbignum -le 'print 0.49999999999999994-0.1'
0.39999999999999994
$ perl -Mbignum -le 'print 0.49999999999999994+10.1'
10.59999999999999994

它透明地将Perl浮点和整数的精度扩展到扩展精度。

答案 1 :(得分:1)

请注意,bignum比内部和其他数学解决方案慢150倍,并且通常不会解决您的问题(只要您需要将数字存储在JSON或数据库或其他任何内容中,您就会遇到同样的问题再次)。

通常情况下,sprintf会照顾你的输出,所以你不必看到丑陋的不精确,但它仍然存在。

这是一个适用于我的x64平台的例子,该平台了解如何处理这种不精确。

这正确地告诉您您感兴趣的2个数字是否相同:

sub safe_eq {
  my($var1,$var2)=@_;
  return 1 if($var1==$var2);
  my $dust;
  if($var2==0) { $dust=abs($var1); }
  else { $dust= abs(($var1/$var2)-1); }
  return 0 if($dust>5.32907051820076e-15 ); # dust <= 5.32907051820075e-15 
  return 1;
}

您可以在此基础上构建以解决所有问题。

它的工作原理是了解原生数字中不精确的程度,并适应它。

答案 2 :(得分:0)

正如你在问题中所说的那样,处理代码中的浮点数很可能是蠕虫,正是因为无论采用何种精度,标准浮点表示都无法准确表示许多十进制数。唯一100%可靠的方法是不使用浮点数

最简单的应用方法是改为使用定点数,尽管这会将精度限制在固定的小数位数。例如,不要存储10.0050,而是定义一个约定,将所有数字存储到4个小数位并改为存储100050。

但根据您为实际尝试完成的内容(构建通用数学库)所做的最小限度解释,这似乎并不能让您满意。然后,下一个选项是将小数位数存储为每个值的缩放因子。因此10.0050将成为包含数据{ value => 100050, scale => 4 }的对象。

然后可以将其扩展为更通用的#34;有理数&#34;数据类型通过有效地存储每个数字作为分子和分母,从而允许您精确存储数字,如1/3,基数2和基数10都不能精确表示。顺便说一下,这是Perl 6所采用的方法。因此,如果切换到Perl 6是一个选项,那么一旦你这样做,你可能会发现这一切都适合你。