Bagaimana Anda membulatkan angka floating point di Perl?

174

Bagaimana saya bisa membulatkan angka desimal (titik mengambang) ke bilangan bulat terdekat?

misalnya

1.2 = 1
1.7 = 2
Ranguard
sumber

Jawaban:

196

Output dari perldoc -q round

Apakah Perl memiliki fungsi round ()? Bagaimana dengan ceil () dan floor ()? Fungsi trigonometri?

Ingatlah bahwa int()hanya memotong ke arah 0. Untuk pembulatan ke angka tertentu, sprintf()atau printf()biasanya rute termudah.

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

The POSIXmodul (bagian dari distribusi Perl standar) menerapkan ceil(), floor()dan sejumlah fungsi matematika dan trigonometri lainnya.

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

Dalam 5.000 hingga 5.003 perl, trigonometri dilakukan dalam Math::Complex modul. Dengan 5.004, Math::Trigmodul (bagian dari distribusi Perl standar) mengimplementasikan fungsi trigonometri. Secara internal ia menggunakan Math::Complexmodul dan beberapa fungsi dapat keluar dari sumbu nyata ke bidang kompleks, misalnya sinus terbalik 2.

Pembulatan dalam aplikasi keuangan dapat memiliki implikasi serius, dan metode pembulatan yang digunakan harus ditentukan secara tepat. Dalam kasus ini, mungkin membayar untuk tidak mempercayai pembulatan sistem mana pun yang digunakan oleh Perl, tetapi untuk mengimplementasikan fungsi pembulatan yang Anda butuhkan sendiri.

Untuk mengetahui alasannya, perhatikan bagaimana Anda masih akan memiliki masalah tentang pergantian setengah jalan:

    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

Jangan salahkan Perl. Itu sama dengan di C. IEEE mengatakan kita harus melakukan ini. Bilangan perl yang nilai absolutnya adalah bilangan bulat di bawah 2**31(pada mesin 32 bit) akan berfungsi seperti bilangan bulat matematika. Nomor lain tidak dijamin.

Vinko Vrsalovic
sumber
17
^ Thariama, mengapa langit-langit akan ditinggalkan? Itu tidak ditinggalkan di POSIX atau perl sejauh yang saya tahu. Kutipan diperlukan!
Sam Watkins
3
@ Pemula, jangan mencoba menggunakan printfjika Anda ingin hasil dalam variabel, gunakan sprintf... harap ini menghemat waktu debugging :-P
Boris Däppen
Bisakah saya menggunakan int()PDL?
CinCout
1
gunakan POSIX; <br/> $ x = ($ x - lantai ($ x)> = .5)? ceil ($ x): floor ($ x);
Joseph Argenio
136

Meskipun tidak setuju dengan jawaban kompleks tentang tanda setengah jalan dan seterusnya, untuk kasus penggunaan yang lebih umum (dan mungkin sepele):

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

MEMPERBARUI

Jika Anda $floatmungkin negatif, variasi berikut akan menghasilkan hasil yang benar:

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

Dengan perhitungan ini, -1.4 dibulatkan menjadi -1, dan -1.6 hingga -2, dan nol tidak akan meledak.

MEMBASAHI
sumber
4
... tetapi gagal pada angka negatif: sprintf masih lebih baik
alessandro
2
Ah tidak, tidak. Membulatkan angka negatif membawa Anda lebih dekat ke nol, tidak jauh. Apa yang mereka ajarkan di sekolah akhir-akhir ini?
RET
6
@ KEMBALI Ya, itu gagal dengan angka negatif. $ float = -1,4 menghasilkan 0 dengan metode ini. Bukan itu yang mereka ajarkan di sekolah saya. Ingat bahwa int () memotong ke nol.
Fishinear
4
@fishinear Kau benar, dan aku dihajar. Tapi saya memang mengatakan 'untuk kasus penggunaan sepele'. Jawaban saya sudah diperbaiki.
RET
1
Perhatikan bahwa $ float = 0, ini akan gagal :-)
mat
74

Anda bisa menggunakan modul seperti Math :: Round :

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

Atau Anda bisa melakukannya dengan cara kasar:

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

Jika Anda memutuskan untuk menggunakan printf atau sprintf, perhatikan bahwa mereka menggunakan metode 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
Kyle
sumber
Terima kasih telah menunjukkan ini. Lebih tepatnya, nama metode ini adalah 'Round Half to Even'.
Jean Vincent
Semua jawaban yang menyebutkan printf, atau sprintf harus menyebutkan ini.
insaner
Ini adalah informasi yang sangat penting. Saya punya bug serveral kali dalam perangkat lunak karena saya berasumsi 5 akan selalu ditangkap. Saya akhirnya menemukan, mengapa perl tidak pernah melakukan apa yang saya inginkan. Terima kasih telah menunjukkan ini.
Boris Däppen
Sebenarnya, ini tergantung OS! Pada Windows itu akan bulat setengah dari nol dan unix-seperti kehendak putaran setengah bahkan: exploringbinary.com/...
APOC
9

Lihat perldoc / perlfaq :

Ingatlah bahwa int()hanya memotong menuju 0. Untuk membulatkan ke angka tertentu, sprintf()atau printf()biasanya merupakan rute termudah.

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

The POSIXmodul (bagian dari distribusi Perl standar) menerapkan ceil(), floor()dan sejumlah fungsi matematika dan trigonometri lainnya.

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

Dalam 5.000 hingga 5.003 perl, trigonometri dilakukan dalam Math::Complexmodul.

Dengan 5,004, Math::Trigmodul (bagian dari distribusi Perl standar)> mengimplementasikan fungsi trigonometri.

Secara internal ia menggunakan Math::Complexmodul dan beberapa fungsi dapat keluar dari sumbu nyata ke bidang kompleks, misalnya sinus terbalik 2.

Pembulatan dalam aplikasi keuangan dapat memiliki implikasi serius, dan metode pembulatan yang digunakan harus ditentukan secara tepat. Dalam kasus ini, mungkin membayar untuk tidak mempercayai pembulatan sistem mana pun yang digunakan oleh Perl, tetapi untuk mengimplementasikan fungsi pembulatan yang Anda butuhkan sendiri.

Untuk mengetahui alasannya, perhatikan bagaimana Anda masih akan memiliki masalah tentang pergantian setengah jalan:

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

Jangan salahkan Perl. Itu sama dengan di C. IEEE mengatakan kita harus melakukan ini. Bilangan perl yang nilai absolutnya adalah bilangan bulat di bawah 2 ** 31 (pada mesin 32 bit) akan berfungsi seperti halnya bilangan bulat matematika. Nomor lain tidak dijamin.

Kent Fredric
sumber
3

Anda tidak memerlukan modul eksternal apa pun.

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

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

Saya mungkin kehilangan poin Anda, tetapi saya pikir ini adalah cara yang jauh lebih bersih untuk melakukan pekerjaan yang sama.

Apa yang dilakukan adalah menelusuri setiap angka positif dalam elemen, mencetak angka dan bilangan bulat bulat dalam format yang Anda sebutkan. Kode ini menyatukan masing-masing bilangan bulat positif bulat hanya berdasarkan desimal. int ($ _) pada dasarnya membulatkan angka jadi ($ -int ($ )) menangkap desimal. Jika desimalnya (menurut definisi) benar-benar kurang dari 0,5, bulatkan jumlahnya. Jika tidak, bulatkan dengan menambahkan 1.

activealexaoki
sumber
1
Sekali lagi, mengapa menjawab pertanyaan kuno dengan jawaban yang rumit ketika sesuatu seperti jawaban RET bekerja dengan baik.
Joel Berger
1
Ini benar-benar tidak terlalu rumit, dan jawaban RET melibatkan banyak matematika yang a) secara teoritis berisiko meluap, b) membutuhkan waktu lebih lama, dan c) tidak perlu memperkenalkan lebih banyak ketidaktepatan fp pada nilai akhir Anda. Tunggu, yang mana yang rumit lagi? ;)
cptstubing06
2

Berikut ini akan membulatkan angka positif atau negatif ke posisi desimal yang diberikan:

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

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

Berikut ini adalah contoh lima cara berbeda untuk merangkum nilai. Yang pertama adalah cara naif untuk melakukan penjumlahan (dan gagal). Upaya kedua untuk digunakan sprintf(), tetapi juga gagal. Penggunaan ketiga sprintf()berhasil sementara dua terakhir (4 & 5) digunakan 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

Catatan yang floor($value + 0.5)dapat diganti dengan int($value + 0.5)untuk menghapus ketergantungan POSIX.

David Beckman
sumber
1

Angka negatif dapat menambahkan beberapa kebiasaan yang perlu diperhatikan orang.

printfPendekatan gaya memberi kita angka yang benar, tetapi mereka dapat menghasilkan beberapa tampilan aneh. Kami telah menemukan bahwa metode ini (menurut saya, bodoh) memberi -tanda apakah harus atau tidak seharusnya. Misalnya, -0,01 dibulatkan ke satu tempat desimal mengembalikan -0,0, bukan hanya 0. Jika Anda akan melakukan printfpendekatan gaya, dan Anda tahu Anda ingin tidak ada desimal, gunakan %ddan tidak %f(ketika Anda membutuhkan desimal, saat itulah tampilan menjadi miring).

Meskipun benar dan untuk matematika bukan masalah besar, untuk tampilan itu hanya terlihat aneh menunjukkan sesuatu seperti "-0.0".

Untuk metode int, angka negatif dapat mengubah apa yang Anda inginkan sebagai hasilnya (meskipun ada beberapa argumen yang dapat membuatnya benar).

The int + 0.5menyebabkan masalah nyata dengan nomor -negatif, kecuali jika Anda ingin bekerja seperti itu, tapi aku membayangkan kebanyakan orang tidak. -0,9 mungkin harus dibulatkan menjadi -1, bukan 0. Jika Anda tahu bahwa Anda ingin negatif menjadi langit-langit daripada lantai maka Anda dapat melakukannya dalam satu-liner, jika tidak, Anda mungkin ingin menggunakan metode int dengan minor modifikasi (ini jelas hanya berfungsi untuk mendapatkan kembali bilangan bulat:

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

Solusi saya untuk 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 );
Akvel
sumber
0

Jika Anda hanya peduli untuk mendapatkan nilai integer dari angka floating point keseluruhan (yaitu 12347.9999 atau 54321.0001), pendekatan ini (dipinjam dan dimodifikasi dari atas) akan melakukan trik:

my $rounded = floor($float + 0.1); 
HoldOffHunger
sumber
0

banyak membaca dokumentasi tentang cara membulatkan angka, banyak ahli menyarankan untuk menulis rutinitas pembulatan Anda sendiri, karena versi 'kalengan' yang disediakan dengan bahasa Anda mungkin tidak cukup tepat, atau mengandung kesalahan. Saya membayangkan, bagaimanapun, mereka berbicara banyak tempat desimal bukan hanya satu, dua, atau tiga. dengan itu dalam pikiran, inilah solusi saya (walaupun tidak PERSIS seperti yang diminta karena kebutuhan saya untuk menampilkan dolar - prosesnya tidak jauh berbeda, meskipun).

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);
}
Jarett Lloyd
sumber
untuk membuat subrutin sesuai dengan spesifikasi Anda, cukup modifikasi berikut ini: if ($digit3 >= 5) { $digit3 = 0; $digit2++; if ($digit2 > 9) { $digit2 = 0; $digit1++; if ($digit1 > 9) { $digit1 = 0; $cost[0]++; } } } jadi: if ($digit1 >= 5) { $digit1 = 0; $cost[0]++; } kemudian sajareturn commafied($cost[0]);
Jarett Lloyd
-2
cat table |
  perl -ne '/\d+\s+(\d+)\s+(\S+)/ && print "".**int**(log($1)/log(2))."\t$2\n";' 
Steven Penny
sumber