【问题标题】:How do you round a floating point number in Perl?你如何在 Perl 中对浮点数进行四舍五入?
【发布时间】:2010-09-15 18:22:37
【问题描述】:

如何将十进制数(浮点数)四舍五入到最接近的整数?

例如

1.2 = 1
1.7 = 2

【问题讨论】:

    标签: perl floating-point rounding


    【解决方案1】:

    perldoc -q round的输出

    Perl 有 round() 函数吗? ceil() 和 floor() 呢? 三角函数?

    请记住,int() 只是向0 截断。对于特定位数,sprintf()printf() 通常是最简单的 路线。

        printf("%.3f", 3.1415926535);       # prints 3.142
    

    POSIX 模块(标准 Perl 发行版的一部分)实现 ceil()floor() 和许多其他数学和三角函数 职能。

        use POSIX;
        $ceil   = ceil(3.5);                        # 4
        $floor  = floor(3.5);                       # 3
    

    在 5.000 到 5.003 perls 中,三角函数在 Math::Complex 中完成 模块。在 5.004 中,Math::Trig 模块(标准 Perl 的一部分 分布)实现三角函数。在内部它 使用Math::Complex 模块,一些功能可以从 实轴进入复平面,例如 2 的反正弦。

    金融应用程序中的四舍五入可能会产生严重影响,并且 应精确指定使用的舍入方法。在这些 在这种情况下,不信任正在进行的任何系统舍入可能是值得的 Perl 使用,而是实现您需要的舍入函数 你自己。

    要了解原因,请注意您仍然会在中途遇到问题 交替:

        for ($i = 0; $i < 1.01; $i += 0.05) { printf "%.1f ",$i}
    
        0.0 0.1 0.1 0.2 0.2 0.2 0.3 0.3 0.4 0.4 0.5 0.5 0.6 0.7 0.7
        0.8 0.8 0.9 0.9 1.0 1.0
    

    不要责怪 Perl。这和 C 中的一样。IEEE 说我们必须做 这。绝对值为2**31 下的整数的 Perl 数(在 32 位机器)将非常像数学整数一样工作。 不保证其他数字。

    【讨论】:

    • ^ Thariama,为什么不推荐使用 ceil?据我所知,它在 POSIX 或 perl 中没有被弃用。需要引用!
    • @Beginners,不要尝试使用printf,如果你想要变量中的结果,使用sprintf...希望这可以节省你一些调试时间:-P
    • 我可以在 PDL 上使用 int() 吗?
    • 使用 POSIX;
      $x = ($x - floor($x) >= .5) ? ceil($x) : 楼层($x);
    【解决方案2】:

    虽然不同意关于中途标记等的复杂答案,但对于更常见(并且可能微不足道)的用例:

    my $rounded = int($float + 0.5);

    更新

    如果您的$float 可能为负数,则以下变体将产生正确的结果:

    my $rounded = int($float + $float/abs($float*2 || 1));

    通过此计算,-1.4 舍入为 -1,-1.6 舍入为 -2,零不会爆炸。

    【讨论】:

    • ...但它在负数上失败:还是更好的 sprintf
    • 啊不,它没有。将负数四舍五入会使您更接近零,而不是更远。这些天他们在学校教什么?
    • @RET 是的,它确实因负数而失败。 $float=-1.4 使用此方法的结果为 0。那不是他们在我学校教的。请记住 int() 会向零截断。
    • @fishinear 你是对的,我受到了应有的惩罚。但我确实说过'用于琐碎的用例'。我的答案已更正。
    • 注意$float = 0,这会失败:-)
    【解决方案3】:

    您可以使用 Math::Round 之类的模块:

    use Math::Round;
    my $rounded = round( $float );
    

    或者你可以用粗略的方式来做:

    my $rounded = sprintf "%.0f", $float;
    

    【讨论】:

      【解决方案4】:

      如果您决定使用 printf 或 sprintf,请注意它们使用 Round half to even 方法。

      foreach my $i ( 0.5, 1.5, 2.5, 3.5 ) {
          printf "$i -> %.0f\n", $i;
      }
      __END__
      0.5 -> 0
      1.5 -> 2
      2.5 -> 2
      3.5 -> 4
      

      【讨论】:

      • 感谢您指出这一点。更准确地说,该方法的名称是“Round Half to Even”。
      • 所有提到 printf 或 sprintf 的答案都应该提到这一点。
      • 这是一个极其重要的信息。我在软件中有几次错误,因为我认为 5 总是会被四舍五入。我终于找到了,为什么 perl 从来没有做我想做的事。感谢您指出这一点。
      • 实际上,这取决于操作系统!在 Windows 中,它将从零舍入一半,而类 unix 将舍入一半到偶数:exploringbinary.com/…
      • @Apoc 在IEEE754 中被定义为“四舍五入到最近的(整数)关系到偶数”。 Nearest 是距离小于 0.5(幅度)的任何整数。如果该数字恰好是正好一半(平局),则四舍五入为偶数。是的,Windows 不遵循 IEEE 规范。
      【解决方案5】:

      perldoc/perlfaq:

      请记住,int() 只是向 0 截断。对于四舍五入为 某些位数,sprintf()printf() 通常是 最简单的路线。

       printf("%.3f",3.1415926535);
       # prints 3.142
      

      POSIX 模块(标准 Perl 发行版的一部分) 实现ceil()floor() 和其他一些数学 和三角函数。

      use POSIX;
      $ceil  = ceil(3.5); # 4
      $floor = floor(3.5); # 3
      

      在 5.000 到 5.003 perls 中,三角函数是在 Math::Complex 模块中完成的。

      在 5.004 中,Math::Trig 模块(标准 Perl 发行版的一部分)> 实现了三角函数。

      在内部它使用Math::Complex 模块,并且某些功能可能会中断 从实轴到复平面,例如2的反正弦。

      金融应用程序中的四舍五入可能会产生严重影响,并且四舍五入 应精确指定使用的方法。在这些情况下,可能不值得 信任 Perl 使用的任何系统舍入,而是实现 自己需要的舍入函数。

      要了解原因,请注意在中途交替时您仍然会遇到问题:

      for ($i = 0; $i < 1.01; $i += 0.05)
      {
         printf "%.1f ",$i
      }
      
      0.0 0.1 0.1 0.2 0.2 0.2 0.3 0.3 0.4 0.4 0.5 0.5 0.6 0.7 0.7 0.8 0.8 0.9 0.9 1.0 1.0
      

      不要责怪 Perl。这和 C 中的一样。IEEE 说我们必须做 这。 Perl 数字的绝对值是小于 2**31 的整数(在 32 位机器)将非常像数学整数一样工作。 不保证其他数字。

      【讨论】:

        【解决方案6】:

        您不需要任何外部模块。

        $x[0] = 1.2;
        $x[1] = 1.7;
        
        foreach (@x){
          print $_.' = '.( ( ($_-int($_))<0.5) ? int($_) : int($_)+1 );
          print "\n";
        }
        

        我可能错过了你的意思,但我认为这是做同样工作的更清洁的方式。

        这样做是遍历元素中的每个正数,以您提到的格式打印数字和四舍五入的整数。该代码仅基于小数连接各自的舍入正整数。 int($_) 基本上 四舍五入 数字所以 ($-int($)) 捕获小数。如果小数(根据定义)严格小于 0.5,则向下取整。如果不是,则加 1 进行四舍五入。

        【讨论】:

        • 再一次,当 RET 的答案同样有效时,为什么要用复杂的答案来回答一个古老的问题。
        • 这真的不是很复杂,RET 的答案涉及到一堆数学问题:a) 理论上存在溢出的风险,b) 需要更长的时间,c) 不必要地为您的最终值引入更多 fp 不精确性。等等,哪一个又复杂了? ;)
        【解决方案7】:

        以下会将正数或负数四舍五入到给定的小数位:

        sub round ()
        {
            my ($x, $pow10) = @_;
            my $a = 10 ** $pow10;
        
            return (int($x / $a + (($x < 0) ? -0.5 : 0.5)) * $a);
        }
        

        【讨论】:

          【解决方案8】:

          以下是对值求和的五种不同方法的示例。第一种是执行求和(并且失败)的幼稚方式。第二次尝试使用sprintf(),但也失败了。第三个成功使用sprintf(),而最后两个(第4和第5个)使用floor($value + 0.5)

           use strict;
           use warnings;
           use POSIX;
          
           my @values = (26.67,62.51,62.51,62.51,68.82,79.39,79.39);
           my $total1 = 0.00;
           my $total2 = 0;
           my $total3 = 0;
           my $total4 = 0.00;
           my $total5 = 0;
           my $value1;
           my $value2;
           my $value3;
           my $value4;
           my $value5;
          
           foreach $value1 (@values)
           {
                $value2 = $value1;
                $value3 = $value1;
                $value4 = $value1;
                $value5 = $value1;
          
                $total1 += $value1;
          
                $total2 += sprintf('%d', $value2 * 100);
          
                $value3 = sprintf('%1.2f', $value3);
                $value3 =~ s/\.//;
                $total3 += $value3;
          
                $total4 += $value4;
          
                $total5 += floor(($value5 * 100.0) + 0.5);
           }
          
           $total1 *= 100;
           $total4 = floor(($total4 * 100.0) + 0.5);
          
           print '$total1: '.sprintf('%011d', $total1)."\n";
           print '$total2: '.sprintf('%011d', $total2)."\n";
           print '$total3: '.sprintf('%011d', $total3)."\n";
           print '$total4: '.sprintf('%011d', $total4)."\n";
           print '$total5: '.sprintf('%011d', $total5)."\n";
          
           exit(0);
          
           #$total1: 00000044179
           #$total2: 00000044179
           #$total3: 00000044180
           #$total4: 00000044180
           #$total5: 00000044180
          

          请注意,floor($value + 0.5) 可以替换为int($value + 0.5) 以消除对POSIX 的依赖。

          【讨论】:

            【解决方案9】:

            负数可能会增加一些人们需要注意的怪癖。

            printf 风格的方法给了我们正确的数字,但它们可能会导致一些奇怪的显示。我们发现这种方法(在我看来是愚蠢的)会添加一个- 符号,无论它是否应该。例如,四舍五入到小数点后一位的 -0.01 会返回 -0.0,而不仅仅是 0。如果您打算采用 printf 风格的方法,并且您知道不需要小数,请使用 %d 而不是 %f (当您需要小数时,显示会变得不稳定)。

            虽然它是正确的,而且对于数学来说没什么大不了的,但对于显示来说,显示“-0.0”之类的东西看起来很奇怪。

            对于 int 方法,负数可以改变你想要的结果(尽管有一些参数可以使它们是正确的)。

            int + 0.5 会导致负数的实际问题,除非您希望它以这种方式工作,但我想大多数人都不会。 -0.9 应该四舍五入到 -1,而不是 0。如果您知道要让负数成为天花板而不是地板,那么您可以在单线中进行,否则,您可能需要使用带有小数的 int 方法修改(这显然只能取回整数:

            my $var = -9.1;
            my $tmpRounded = int( abs($var) + 0.5));
            my $finalRounded = $var >= 0 ? 0 + $tmpRounded : 0 - $tmpRounded;
            

            【讨论】:

              【解决方案10】:

              我的 sprintf 解决方案

              if ($value =~ m/\d\..*5$/){
                  $format =~ /.*(\d)f$/;
                  if (defined $1){
                     my $coef = "0." . "0" x $1 . "05";    
                          $value = $value + $coef;    
                  }
              }
              
              $value = sprintf( "$format", $value );
              

              【讨论】:

                【解决方案11】:

                如果您只关心从整个浮点数(即 12347.9999 或 54321.0001)中获取整数值,那么这种方法(从上面借用和修改)可以解决问题:

                my $rounded = floor($float + 0.1); 
                

                【讨论】:

                  【解决方案12】:

                  使用Math::BigFloat,您可以执行以下操作:

                  use Math::BigFloat;
                  
                  print Math::BigFloat->new(1.2)->bfround(1); ## 1
                  print Math::BigFloat->new(1.7)->bfround(1); ## 2
                  

                  这可以包装在一个子程序中

                  use Math::BigFloat;
                  
                  sub round {
                    Math::BigFloat->new(shift)->bfround(1);
                  }
                  
                  print round(1.2); ## 1
                  print round(1.7); ## 2
                  

                  【讨论】:

                    【解决方案13】:

                    大量阅读有关如何舍入数字的文档,许多专家建议编写自己的舍入例程,因为您的语言提供的“固定”版本可能不够精确,或者包含错误。然而,我想,他们说的是许多小数位,而不仅仅是一位、两位或三位。考虑到这一点,这是我的解决方案(尽管并不完全符合我的要求,因为我需要显示美元——不过,这个过程并没有太大的不同)。

                    sub asDollars($) {
                      my ($cost) = @_;
                      my $rv = 0;
                    
                      my $negative = 0;
                      if ($cost =~ /^-/) {
                        $negative = 1;
                        $cost =~ s/^-//;
                      }
                    
                      my @cost = split(/\./, $cost);
                    
                      # let's get the first 3 digits of $cost[1]
                      my ($digit1, $digit2, $digit3) = split("", $cost[1]);
                      # now, is $digit3 >= 5?
                      # if yes, plus one to $digit2.
                      # is $digit2 > 9 now?
                      # if yes, $digit2 = 0, $digit1++
                      # is $digit1 > 9 now??
                      # if yes, $digit1 = 0, $cost[0]++
                      if ($digit3 >= 5) {
                        $digit3 = 0;
                        $digit2++;
                        if ($digit2 > 9) {
                          $digit2 = 0;
                          $digit1++;
                          if ($digit1 > 9) {
                            $digit1 = 0;
                            $cost[0]++;
                          }
                        }
                      }
                      $cost[1] = $digit1 . $digit2;
                      if ($digit1 ne "0" and $cost[1] < 10) { $cost[1] .= "0"; }
                    
                      # and pretty up the left of decimal
                      if ($cost[0] > 999) { $cost[0] = commafied($cost[0]); }
                    
                      $rv = join(".", @cost);
                    
                      if ($negative) { $rv = "-" . $rv; }
                    
                      return $rv;
                    }
                    
                    sub commafied($) {
                      #*
                      # to insert commas before every 3rd number (from the right)
                      # positive or negative numbers
                      #*
                      my ($num) = @_; # the number to insert commas into!
                    
                      my $negative = 0;
                      if ($num =~ /^-/) {
                        $negative = 1;
                        $num =~ s/^-//;
                      }
                      $num =~ s/^(0)*//; # strip LEADING zeros from given number!
                      $num =~ s/0/-/g; # convert zeros to dashes because ... computers!
                    
                      if ($num) {
                        my @digits = reverse split("", $num);
                        $num = "";
                    
                        for (my $i = 0; $i < @digits; $i += 3) {
                          $num .= $digits[$i];
                          if ($digits[$i+1]) { $num .= $digits[$i+1]; }
                          if ($digits[$i+2]) { $num .= $digits[$i+2]; }
                          if ($i < (@digits - 3)) { $num .= ","; }
                          if ($i >= @digits) { last; }
                        }
                    
                        #$num =~ s/,$//;
                        $num = join("", reverse split("", $num));
                        $num =~ s/-/0/g;
                      }
                    
                      if ($negative) { $num = "-" . $num; }
                    
                      return $num; # a number with commas added
                      #usage: my $prettyNum = commafied(1234567890);
                    }
                    

                    【讨论】:

                    • 要使子程序符合您的规范,只需修改以下内容:if ($digit3 &gt;= 5) { $digit3 = 0; $digit2++; if ($digit2 &gt; 9) { $digit2 = 0; $digit1++; if ($digit1 &gt; 9) { $digit1 = 0; $cost[0]++; } } },因此它是:if ($digit1 &gt;= 5) { $digit1 = 0; $cost[0]++; } 然后只需 return commafied($cost[0]);
                    【解决方案14】:
                    cat table |
                      perl -ne '/\d+\s+(\d+)\s+(\S+)/ && print "".**int**(log($1)/log(2))."\t$2\n";' 
                    

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多