gpt4 book ai didi

Perl 再次舍入错误

转载 作者:行者123 更新时间:2023-12-04 09:24:36 24 4
gpt4 key购买 nike

显示问题的示例:

  • 拥有数字 105;
  • 除以 1000(结果 0.105)
  • 小数点后 2 位应为:0.11

  • 现在,几个脚本 - 基于另一个问题的答案:

    这是大多数推荐和大多数赞成的解决方案使用 printf .
    use 5.014;
    use warnings;
    my $i = 105;
    printf "%.2f\n", $i/1000; #prints 0.10

    但打印出错误的结果。在评论中
    https://stackoverflow.com/a/1838885 @Sinan Unur 说(6 次点赞评论):

    Use sprintf("%.3f", $value) for mathematical purposes too.



    但是,它“有时”不起作用......就像上面一样。

    另一个推荐的解决方案 Math::BigFloat :
    use 5.014;
    use warnings;
    use Math::BigFloat;
    my $i = 105;

    Math::BigFloat->precision(-2);
    my $r = Math::BigFloat->new($i/1000);

    say "$r"; #0.10 ;(

    结果也错了。另一个推荐的 bignum :
    use 5.014;
    use warnings;
    use bignum ( p => -2 );
    my $i = 105;
    my $r = $i/1000;
    say "$r"; #0.10 ;(

    又错了。 ;(

    现在工作的:
    use 5.014;
    use warnings;
    use Math::Round;
    my $i = 105;
    say nearest(0.01, $i/1000); #GREAT prints 0.11 :)

    好结果 0.11 ,但是这里的评论 https://stackoverflow.com/a/571740
    提示它。

    最后是另一个“由我自己”功能的推荐:
    use 5.014;
    use warnings;
    my $i = 105;
    my $f = $i/1000;

    say myround($f,2); # 0.11

    sub myround {
    my($float, $prec) = @_;
    my $f = $float * (10**$prec);
    my $r = int($f + $f/abs($f*2));
    return $r/(10**$prec);
    }

    版画 0.11也是,但不能证明它的正确性。

    对于我阅读的引用:
  • How do you round a floating point number in Perl?
  • In Perl, how can I limit the number of places after the decimal point but have no trailing zeroes?
  • How do I set the floating point precision in Perl?
  • 以及许多其他...
  • 最后这也是:
    http://www.exploringbinary.com/inconsistent-rounding-of-printed-floating-point-numbers/
    是什么给出了 真的很好对问题的总体看法。

  • 我明白这是所有语言的通病,但毕竟请
    以上阅读 - 我仍然有这个问题:

    什么是 防错 perl 中将浮点数四舍五入为 N 的方法
    小数位 - 以数学上正确的方式,例如结果会怎样
    喜欢 105/1000正确到 N 位小数,没有“惊喜”...

    最佳答案

    当数字正好是 0.105 时,您会期待特定的行为,但浮点错误意味着您不能期望一个数字与您认为的完全一样。

    105/1000 是二进制的周期数,就像 1/3 是十进制的周期数。

    105/1000
    ____________________
    = 0.00011010111000010100011 (bin)

    ~ 0.00011010111000010100011110101110000101000111101011100001 (bin)

    = 0.10499999999999999611421941381195210851728916168212890625

    0.1049999... 小于 0.105,因此四舍五入为 0.10。

    但即使你正好有 0.105,自 sprintf 起,它仍然会四舍五入到 0.10。四舍五入到一半。更好的测试是 155/1000
    155/1000
    ____________________
    = 0.00100111101011100001010 (bin)

    ~ 0.0010011110101110000101000111101011100001010001111010111 (bin)

    = 0.1549999999999999988897769753748434595763683319091796875

    0.155 应该四舍五入到 0.16,但由于浮点错误,它四舍五入到 0.15。
    $ perl -E'$_ = 155; say sprintf("%.2f", $_/1000);'
    0.15

    $ perl -E'$_ = 155; say sprintf("%.0f", $_/10)/100;'
    0.16

    第二个有效,因为 5/10 不是周期性的,这就是解决方案。正如 Sinan Unur 所说,您可以使用 sprintf 更正错误。 .但是如果你不想丢失你的工作,你必须四舍五入到一个整数。
    $ perl -E'
    $_ = 155/1000;

    $_ *= 1000; # Move decimal point past significant.
    $_ = sprintf("%.0f", $_); # Fix floating-point error.
    $_ /= 10; # 5/10 is not periodic
    $_ = sprintf("%.0f", $_); # Do our rounding.
    $_ /= 100; # Restore decimal point.

    say;
    '
    0.16

    这将修复舍入误差,允许 sprintf正确舍入一半到均匀。
    0.105  =>  0.10
    0.115 => 0.12
    0.125 => 0.12
    0.135 => 0.14
    0.145 => 0.14
    0.155 => 0.16
    0.165 => 0.16

    如果你想四舍五入,你需要使用除 sprintf 以外的其他东西。做最后的舍入。或者您可以添加 s/5\z/6/;在除以 10 之前。

    但这很复杂。

    答案的第一句话是关键。当数字正好是 0.105 时,您会期待特定的行为,但浮点错误意味着您不能期望一个数字与您认为的完全一样。解决方案是引入容差。这就是使用 sprintf 的舍入确实如此,但它是一个生硬的工具。
    use strict;
    use warnings;
    use feature qw( say );

    use POSIX qw( ceil floor );

    sub round_half_up {
    my ($num, $places, $tol) = @_;

    my $mul = 1; $mul *= 10 for 1..$places;
    my $sign = $num >= 0 ? +1 : -1;

    my $scaled = $num * $sign * $mul;
    my $frac = $scaled - int($scaled);

    if ($sign >= 0) {
    if ($frac < 0.5-$tol) {
    return floor($scaled) / $mul;
    } else {
    return ceil($scaled) / $mul;
    }
    } else {
    if ($frac < 0.5+$tol) {
    return -floor($scaled) / $mul;
    } else {
    return -ceil($scaled) / $mul;
    }
    }
    }


    say sprintf '%5.2f', round_half_up( 0.10510000, 2, 0.00001);  #  0.11
    say sprintf '%5.2f', round_half_up( 0.10500001, 2, 0.00001); # 0.11 Within tol
    say sprintf '%5.2f', round_half_up( 0.10500000, 2, 0.00001); # 0.11 Within tol
    say sprintf '%5.2f', round_half_up( 0.10499999, 2, 0.00001); # 0.11 Within tol
    say sprintf '%5.2f', round_half_up( 0.10410000, 2, 0.00001); # 0.10
    say sprintf '%5.2f', round_half_up(-0.10410000, 2, 0.00001); # -0.10
    say sprintf '%5.2f', round_half_up(-0.10499999, 2, 0.00001); # -0.10 Within tol
    say sprintf '%5.2f', round_half_up(-0.10500000, 2, 0.00001); # -0.10 Within tol
    say sprintf '%5.2f', round_half_up(-0.10500001, 2, 0.00001); # -0.10 Within tol
    say sprintf '%5.2f', round_half_up(-0.10510000, 2, 0.00001); # -0.11

    可能存在沿着相同路线工作的现有解决方案。

    关于Perl 再次舍入错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24493228/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com