Perlで浮動小数点数をどのように丸めますか?


回答:


196

の出力 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
^タリアマ、なぜceilが非推奨になるのですか?私の知る限り、POSIXやperlでは非推奨ではありません。引用が必要です!
Sam Watkins

3
@Beginners、printf結果を変数にしたい場合は使用しないでください。使用してくださいsprintf。これにより、デバッグ時間を節約できます:-P
BorisDäppenMar

int()PDLで使用できますか?
CinCout 2017年

1
POSIXを使用します。<br/> $ x =($ x-floor($ x)> = .5)?ceil($ x):floor($ x);
Joseph Argenio 2017年

136

中間点などに関する複雑な回答には同意しませんが、より一般的な(そして些細な)ユースケースについては、次のようになります。

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

更新

$floatがマイナスになる可能性がある場合は、次のバリエーションで正しい結果が得られます。

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

この計算では、-1.4は-1に丸められ、-1.6は-2に丸められ、ゼロは爆発しません。


4
...しかし、負の数では失敗します:sprintfの方が優れています
アレッサンドロ2012年

2
ああ、そうではありません。負の数を切り上げると、ゼロに近づくことができます。彼らは最近学校で何を教えていますか?
RET

6
@RETはい、負の数で失敗します。$ float = -1.4は、このメソッドでは0になります。彼らは私の学校でそれを教えていません。int()はゼロに向かって切り捨てられることに注意してください。
fishinear

4
@fishinearあなたは正しい、そして私は正当に懲らしめられている。しかし、私は「ささいなユースケースのために」と言いました。私の答えは修正されました。
RET

1
$ float = 0であることに注意してください。これは失敗します:-)
マット

74

Math :: Roundのようなモジュールを使用できます。

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

または、大雑把な方法でそれを行うことができます:

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

46

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」です。
Jean Vincent

printfまたはsprintfに関するすべての回答でこれについて言及する必要があります。
insaner 2016

これは非常に重要な情報です。5は常に切り上げられると想定していたため、ソフトウェアにサーバータイムのバグがありました。私はついに、なぜperlが私が望んでいたことをしなかったのかを見つけました。これを指摘してくれてありがとう。
BorisDäppen2017

実際、これはOSに依存しています。:Windowsでは、それは半分離れてゼロとUnixライクな意志ラウンドの半分からでもへの丸めますexploringbinary.com/...
黙示録

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はこれを実行する必要があると言っています。絶対値が2 ** 31(32ビットマシン上)未満の整数であるPerl番号は、数学的な整数とほとんど同じように機能します。その他の数値は保証されません。


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 2013年

2

次の例では、正または負の数値を指定された小数点位置に丸めます。

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

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

1

以下は、値を合計する5つの異なる方法のサンプルです。1つ目は、合計を実行する素朴な方法です(失敗します)。2番目はを使用しようとしますがsprintf()、失敗します。sprintf()最後の2つ(4番目と5番目)の使用中に、3番目は正常に使用されます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スタイルのアプローチでは正しい数が得られますが、奇妙な表示になる場合があります。この方法(私の意見では、愚かに)が、-それをすべきか、すべきでないかに関わらず、サインを入れることを発見しました。たとえば、小数点以下1桁に丸められた-0.01は、単に0ではなく-0.0を返します。printfスタイルアプローチを実行する予定で、小数点が不要であることがわかっている場合は、使用%dせずに使用%fします(小数点が必要な場合は、表示が不安定になります)。

それは正しく、数学的には大した問題ではありませんが、表示の場合は「-0.0」のような奇妙な表示になります。

intメソッドの場合、結果として負の数を使用すると、必要な値が変わる可能性があります(ただし、引数を正しいものにすることができるいくつかの引数があります)。

int + 0.5あなたはそれがそのように動作する場合を除き、陰性の数字で、実際の問題の原因となるが、私はほとんどの人がいない想像します。-0.9はおそらく0ではなく-1に丸める必要があります。負の値を床ではなく天井にしたい場合は、ワンライナーで実行できます。それ以外の場合は、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

数値を丸める方法に関するドキュメントを大量に読む場合、多くの専門家は独自の丸めルーチンを作成することを提案します。これは、言語で提供されている「缶詰」バージョンが十分に正確でないか、エラーが含まれている可能性があるためです。しかし、彼らは、1、2、または3だけでなく、多くの小数点以下の桁数を話していると思います。それを念頭に置いて、ここに私の解決策があります(私の要求はドルを表示する必要があるため、要求どおりには正確ではありませんが、プロセスには大きな違いはありません)。

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ロイド

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.