Làm thế nào để bạn làm tròn số dấu phẩy động trong Perl?


174

Làm cách nào tôi có thể làm tròn số thập phân (dấu phẩy động) sang số nguyên gần nhất?

ví dụ

1.2 = 1
1.7 = 2

Câu trả lời:


196

Đầu ra của perldoc -q round

Perl có hàm round () không? Còn trần () và sàn () thì sao? Chức năng trig?

Hãy nhớ rằng int()chỉ đơn giản là cắt ngắn về phía 0. Để làm tròn đến một số chữ số nhất định, sprintf()hoặc printf()thường là con đường dễ nhất.

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

Các POSIXmô-đun (một phần của phân phối Perl tiêu chuẩn) dụng cụ ceil(), floor()và một số chức năng toán học và lượng giác khác.

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

Trong 5.000 đến 5,003 perls, lượng giác đã được thực hiện trong Math::Complex mô-đun. Với 5,004, Math::Trigmô-đun (một phần của phân phối Perl tiêu chuẩn) thực hiện các hàm lượng giác. Trong nội bộ, nó sử dụng Math::Complexmô-đun và một số chức năng có thể thoát ra khỏi trục thực vào mặt phẳng phức, ví dụ sin ngược của 2.

Làm tròn trong các ứng dụng tài chính có thể có ý nghĩa nghiêm trọng và phương pháp làm tròn được sử dụng phải được chỉ định chính xác. Trong những trường hợp này, có thể trả tiền không tin tưởng bất kỳ hệ thống làm tròn nào đang được Perl sử dụng, mà thay vào đó để thực hiện chức năng làm tròn mà bạn cần cho mình.

Để biết lý do tại sao, hãy chú ý cách bạn vẫn gặp sự cố khi thay đổi nửa điểm:

    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

Đừng đổ lỗi cho Perl. Nó giống như trong C. IEEE nói rằng chúng ta phải làm điều này. Các số Perl có giá trị tuyệt đối là số nguyên dưới 2**31(trên máy 32 bit) sẽ hoạt động khá giống với số nguyên toán học. Các số khác không được đảm bảo.


17
^ Thariama, tại sao trần nhà sẽ bị phản đối? Nó không bị phản đối trong POSIX hoặc perl theo như tôi biết. Cần dẫn nguồn!
Sam Watkins

3
@Beginners, đừng cố sử dụng printfnếu bạn muốn kết quả trong một biến, hãy sử dụng sprintf... hy vọng điều này sẽ giúp bạn tiết kiệm thời gian gỡ lỗi :-P
Boris Däppen

Tôi có thể sử dụng int()trên PDL không?
CinCout

1
sử dụng POSIX; <br/> $ x = ($ x - sàn ($ x)> = .5)? trần ($ x): sàn ($ x);
Joseph Argenio

136

Mặc dù không đồng ý với các câu trả lời phức tạp về các dấu hiệu nửa vời, v.v., đối với trường hợp sử dụng phổ biến hơn (và có thể tầm thường):

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

CẬP NHẬT

Nếu bạn $floatcó thể âm tính, biến thể sau đây sẽ tạo ra kết quả chính xác:

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

Với phép tính này, .41.4 được làm tròn thành -1 và -1.6 đến -2 và không nổ.


4
... nhưng nó không thành công trên các số âm: vẫn chạy nước rút tốt hơn
alessandro

2
À không, không. Làm tròn số âm sẽ đưa bạn đến gần số 0, không xa hơn. Những gì họ đang dạy trong trường học những ngày này?
RET

6
@RET Có, nó không thành công với số âm. $ float = -1.4 kết quả bằng 0 với phương thức này. Đó không phải là những gì họ dạy ở trường của tôi. Hãy nhớ rằng int () cắt ngắn về không.
fishinear

4
@fishinear Bạn nói đúng, và tôi thật sự bị trừng phạt. Nhưng tôi đã nói 'cho trường hợp sử dụng tầm thường'. Câu trả lời của tôi đã được sửa chữa.
RET

1
Lưu ý rằng nó $ float = 0, điều này sẽ thất bại :-)
mat

74

Bạn có thể sử dụng một mô-đun như Math :: Round :

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

Hoặc bạn có thể làm theo cách thô thiển:

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

46

Nếu bạn quyết định sử dụng printf hoặc sprintf, lưu ý rằng họ sử dụng phương pháp Round Half để chẵn .

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

Cảm ơn đã chỉ ra điều này. Chính xác hơn, tên của phương thức là 'Vòng một nửa đến chẵn'.
Jean Vincent

Tất cả các câu trả lời đề cập đến printf, hoặc sprintf nên đề cập đến điều này.
điên cuồng

Đây là một thông tin cực kỳ quan trọng. Tôi đã gặp lỗi máy chủ trong phần mềm vì tôi giả sử 5 sẽ luôn được làm tròn. Cuối cùng tôi đã tìm thấy, tại sao perl không bao giờ làm những gì tôi muốn. Cảm ơn đã chỉ ra điều này.
Boris Däppen

Trên thực tế, đây là hệ điều hành phụ thuộc! Trong Windows nó sẽ tròn nửa xa zero và unix-like ý chí nửa vòng để thậm chí: exploringbinary.com/...
Apoc

9

Xem perldoc / perlfaq :

Hãy nhớ rằng int()chỉ đơn giản là cắt ngắn về 0. Để làm tròn đến một số chữ số nhất định, sprintf()hoặc printf()thường là con đường dễ nhất.

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

Các POSIXmô-đun (một phần của phân phối Perl tiêu chuẩn) dụng cụ ceil(), floor()và một số chức năng toán học và lượng giác khác.

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

Trong 5.000 đến 5,003 perls, lượng giác đã được thực hiện trong Math::Complexmô-đun.

Với 5,004, Math::Trigmô-đun (một phần của phân phối Perl tiêu chuẩn)> thực hiện các hàm lượng giác.

Trong nội bộ, nó sử dụng Math::Complexmô-đun và một số chức năng có thể thoát ra khỏi trục thực vào mặt phẳng phức, ví dụ sin ngược của 2.

Làm tròn trong các ứng dụng tài chính có thể có ý nghĩa nghiêm trọng và phương pháp làm tròn được sử dụng phải được chỉ định chính xác. Trong những trường hợp này, có thể trả tiền không tin tưởng bất kỳ hệ thống làm tròn nào đang được Perl sử dụng, mà thay vào đó để thực hiện chức năng làm tròn mà bạn cần cho mình.

Để biết lý do tại sao, hãy chú ý cách bạn vẫn gặp sự cố khi thay đổi nửa điểm:

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

Đừng đổ lỗi cho Perl. Nó giống như trong C. IEEE nói rằng chúng ta phải làm điều này. Các số Perl có giá trị tuyệt đối là số nguyên dưới 2 ** 31 (trên máy 32 bit) sẽ hoạt động khá giống với số nguyên toán học. Các số khác không được đảm bảo.


3

Bạn không cần bất kỳ mô-đun bên ngoài.

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

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

Tôi có thể thiếu quan điểm của bạn, nhưng tôi nghĩ rằng đây là cách sạch sẽ hơn nhiều để làm cùng một công việc.

Điều này làm là đi qua mọi số dương trong phần tử, in số và số nguyên được làm tròn theo định dạng bạn đã đề cập. Mã nối các số nguyên dương được làm tròn tương ứng chỉ dựa trên số thập phân. int ($ _) về cơ bản làm tròn số để so ($ -int ($ )) nắm bắt các số thập phân. Nếu số thập phân là (theo định nghĩa) đúng nhỏ hơn 0,5, làm tròn số. Nếu không, làm tròn bằng cách thêm 1.


1
Một lần nữa, tại sao trả lời một câu hỏi cổ xưa bằng một câu trả lời phức tạp khi một câu trả lời như câu trả lời của RET hoạt động tốt như nhau.
Joel Berger

1
Điều này thực sự không quá phức tạp và câu trả lời của RET liên quan đến một loạt toán học mà a) về mặt lý thuyết có nguy cơ tràn, b) mất nhiều thời gian hơn và c) không cần thiết phải giới thiệu thêm fp cho giá trị cuối cùng của bạn. Đợi đã, cái nào phức tạp nữa? ;)
cptstubing06

2

Sau đây sẽ làm tròn các số dương hoặc âm đến một vị trí thập phân nhất định:

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

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

1

Sau đây là một mẫu của năm cách khác nhau để tổng hợp các giá trị. Đầu tiên là một cách ngây thơ để thực hiện tổng kết (và thất bại). Lần thử thứ hai để sử dụng sprintf(), nhưng nó quá thất bại. Cái thứ ba sử dụng sprintf()thành công trong khi hai cái cuối cùng (thứ 4 & 5) sử dụng 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

Lưu ý rằng floor($value + 0.5)có thể được thay thế bằng int($value + 0.5)để loại bỏ sự phụ thuộc vào POSIX.


1

Số âm có thể thêm một số quirks mà mọi người cần phải nhận thức được.

printfphương pháp tiếp cận kiểu cho chúng ta số chính xác, nhưng chúng có thể dẫn đến một số màn hình kỳ lạ. Chúng tôi đã phát hiện ra rằng phương pháp này (theo ý kiến ​​của tôi, một cách ngu ngốc) đưa vào một -dấu hiệu cho dù nó nên hay không nên. Ví dụ: -0,01 được làm tròn đến một vị trí thập phân trả về -0.0, thay vì chỉ 0. Nếu bạn định thực hiện printfphương pháp kiểu và bạn biết bạn không muốn số thập phân, hãy sử dụng %dvà không %f(khi bạn cần số thập phân, đó là khi hiển thị trở nên mạnh mẽ).

Mặc dù nó đúng và đối với toán học không phải là vấn đề lớn, để hiển thị, nó trông có vẻ kỳ lạ khi hiển thị một cái gì đó như "-0.0".

Đối với phương thức int, các số âm có thể thay đổi kết quả bạn muốn (mặc dù có một số đối số có thể được thực hiện chúng là chính xác).

Các int + 0.5nguyên nhân gây các vấn đề thực sự với số -phủ định, trừ khi bạn muốn nó hoạt động theo cách đó, nhưng tôi tưởng tượng hầu hết mọi người thì không. -0.9 có lẽ nên làm tròn thành -1, không phải 0. Nếu bạn biết rằng bạn muốn âm là trần chứ không phải là sàn thì bạn có thể thực hiện trong một lớp lót, nếu không, bạn có thể muốn sử dụng phương thức int với một phụ sửa đổi (điều này rõ ràng chỉ hoạt động để lấy lại toàn bộ số:

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

0

Giải pháp của tôi cho chạy nước rút

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

Nếu bạn chỉ quan tâm đến việc lấy một giá trị số nguyên trong toàn bộ số dấu phẩy động (ví dụ 12347.9999 hoặc 54321.0001), phương pháp này (mượn và sửa đổi từ trên) sẽ thực hiện thủ thuật:

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

0

vô số tài liệu đọc về cách làm tròn số, nhiều chuyên gia khuyên bạn nên viết thói quen làm tròn của riêng bạn, vì phiên bản 'đóng hộp' được cung cấp với ngôn ngữ của bạn có thể không đủ chính xác hoặc có lỗi. tôi tưởng tượng, tuy nhiên, họ đang nói nhiều vị trí thập phân không chỉ một, hai hoặc ba. với ý nghĩ đó, đây là giải pháp của tôi (mặc dù không chính xác như yêu cầu vì nhu cầu của tôi là hiển thị đô la - tuy nhiên quá trình này không khác nhiều).

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);
}

để làm cho chương trình con phù hợp với thông số kỹ thuật của bạn, chỉ cần sửa đổi các điều sau: if ($digit3 >= 5) { $digit3 = 0; $digit2++; if ($digit2 > 9) { $digit2 = 0; $digit1++; if ($digit1 > 9) { $digit1 = 0; $cost[0]++; } } } vì vậy: if ($digit1 >= 5) { $digit1 = 0; $cost[0]++; } đó làreturn commafied($cost[0]);
Jarett Lloyd

-2
cat table |
  perl -ne '/\d+\s+(\d+)\s+(\S+)/ && print "".**int**(log($1)/log(2))."\t$2\n";' 
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.