如何在Perl中舍入浮点数?


Answers:


197

输出 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在32位计算机上,绝对值是整数的Perl数字将非常类似于数学整数。不保证其他数字。


17
^ Thariama,为何不赞成使用ceil?据我所知,它没有在POSIX或perl中被弃用。需要引用!
山姆·沃特金斯

4
@Beginners,printf如果要在变量中使用结果,请不要尝试使用,而是使用sprintf...希望这可以节省一些调试时间:-P
BorisDäppen16年

我可以int()在PDL上使用吗?
CinCout

1
使用POSIX; <br/> $ x =($ x-floor($ x)> = .5)吗?ceil($ x):底数($ x);
Joseph Argenio '17

139

对于不同寻常的用例,虽然不同意关于中途分数等的复杂答案,但您可以:

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

更新

如果您可能$float会否定,以下变化将产生正确的结果:

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

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


4
...但是它在负数上失败:仍然更好的sprintf
亚历山德罗(Alessandro)

2
啊,不,不是。四舍五入一个负数会使您接近零,而不是更远。他们最近在学校教什么?
RET

6
@RET是的,它确实会失败,并带有负数。$ float = -1.4的结果为0。那不是他们在我学校教的。请记住,int()会截断为零。
fishinear

4
@fishinear你是正确的,我受到了适当的惩罚。但是我确实说过“针对琐碎的用例”。我的答案已得到纠正。
RET 2012年

1
需要注意的是它$浮= 0,这将失败:-)

76

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

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

或者,您也可以使用粗略的方法:

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

46

如果您决定使用printf或sprintf,请注意它们使用了舍入半舍入法。

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的答案都应该提到这一点。
insaner '16

这是极其重要的信息。我在软件中遇到过服务器错误,因为我认为5总是会四舍五入。我终于找到了,为什么perl从未做到我想要的。感谢您指出了这一点。
鲍里斯·达彭(BorisDäppen)'17年

实际上,这取决于操作系统!在Windows中,它会从零舍入到一半,而与Unix相似的则将舍入到一半,甚至变为偶数:exploringbinary.com/…–
Apoc

@Apoc在IEEE754中被定义为“到偶数的舍入到最近(整数)联系” 。最接近的是距离小于0.5(大小)的任何整数。如果碰巧数字恰好是一半(平局),则四舍五入为偶数。是的,Windows不遵循IEEE规范。
以撒

9

参见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位计算机上)将非常类似于数学整数。不保证其他数字。


3

您不需要任何外部模块。

$x[0] = 1.2;
$x[1] = 1.7;

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

我可能会遗漏您的观点,但我认为这是完成同一工作的更简洁的方法。

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


1
再说一次,当RET的答案同样有效时,为什么用复杂的答案来回答一个古老的问题。
Joel Berger

1
这确实不是很复杂,RET的答案涉及一堆数学,其中a)理论上有溢出的风险,b)需要更长的时间,并且c)不必要地给最终值带来了更多的fp不精确性。等一下,哪一个又复杂了?;)
cptstubing06

2

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

sub round ()
{
    my ($x, $pow10) = @_;
    my $a = 10 ** $pow10;

    return (int($x / $a + (($x < 0) ? -0.5 : 0.5)) * $a);
}

1

以下是对值求和的五种不同方式的示例。第一种是幼稚的方法来执行求和(并失败)。第二次尝试使用sprintf(),但是也失败了。第三次sprintf()成功使用,而最后两次(第四次和第五次)使用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


1

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

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;

0

我的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 );

0

如果只考虑从整个浮点数中获取一个整数值(即12347.9999或54321.0001),则此方法(从上面借用和修改)将达到目的:

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

0

在阅读有关如何四舍五入的文档方面,许多专家建议编写自己的四舍五入例程,因为随语言提供的“罐头”版本可能不够精确或包含错误。我想象,但是,他们在谈论许多小数位,而不仅仅是一,二或三。考虑到这一点,这是我的解决方案(尽管并非完全按照我的要求来显示美元,但是过程并没有太大不同)。

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 >= 5) { $digit3 = 0; $digit2++; if ($digit2 > 9) { $digit2 = 0; $digit1++; if ($digit1 > 9) { $digit1 = 0; $cost[0]++; } } } 这样 if ($digit1 >= 5) { $digit1 = 0; $cost[0]++; }return commafied($cost[0]);
Jarett Lloyd

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.