Jak zaokrąglić liczbę zmiennoprzecinkową w Perlu?

174

Jak zaokrąglić liczbę dziesiętną (zmiennoprzecinkową) do najbliższej liczby całkowitej?

na przykład

1.2 = 1
1.7 = 2
Ranguard
źródło

Odpowiedzi:

196

Wyjście perldoc -q round

Czy Perl ma funkcję round ()? A co z ceil () i floor ()? Funkcje wyzwalania?

Pamiętaj, że int()po prostu skraca się w kierunku 0. Zaokrągla do określonej liczby cyfr sprintf()lub printf()jest zwykle najłatwiejszą trasą.

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

POSIXModuł (część standardowego rozkładu Perl) narzędzia ceil(), floor()oraz szereg innych funkcji matematycznych i trygonometrycznych.

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

Od 5000 do 5,003 perlów trygonometria została wykonana w Math::Complex module. W 5.004 Math::Trigmoduł (część standardowej dystrybucji Perla) implementuje funkcje trygonometryczne. Wewnętrznie wykorzystuje Math::Complexmoduł, a niektóre funkcje mogą wyłamać się z rzeczywistej osi na płaszczyznę zespoloną, na przykład odwrotny sinus 2.

Zaokrąglanie wniosków finansowych może mieć poważne konsekwencje, dlatego należy dokładnie określić zastosowaną metodę zaokrąglania. W takich przypadkach prawdopodobnie opłaca się nie ufać jakimkolwiek zaokrągleniom systemowym używanym przez Perl, ale zamiast tego zaimplementować funkcję zaokrąglania, której potrzebujesz.

Aby zobaczyć, dlaczego, zwróć uwagę, że nadal będziesz mieć problem z przemianą w połowie punktu:

    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

Nie wiń Perla. Jest tak samo jak w C. IEEE mówi, że musimy to zrobić. Liczby Perla, których wartości bezwzględne są liczbami całkowitymi poniżej 2**31(na komputerach 32-bitowych), będą działać podobnie jak matematyczne liczby całkowite. Inne numery nie są gwarantowane.

Vinko Vrsalovic
źródło
17
^ Thariama, dlaczego ceil miałby być deprecjonowany? O ile wiem, nie jest przestarzała w POSIX lub perl. Wymagany cytat!
Sam Watkins
3
@Beginners, nie próbuj używać, printfjeśli chcesz, aby wynik był w zmiennej, użyj sprintf...
miej
Czy mogę używać int()na PDL?
CinCout
1
używać POSIX; <br/> $ x = ($ x - piętro ($ x)> = .5)? ceil ($ x): floor ($ x);
Joseph Argenio
136

Nie zgadzając się ze złożonymi odpowiedziami dotyczącymi znaków w połowie i tak dalej, dla bardziej powszechnego (i być może trywialnego) przypadku użycia:

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

AKTUALIZACJA

Jeśli możliwe, że twój wynik $floatbędzie ujemny, następująca zmiana da prawidłowy wynik:

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

Przy takim obliczeniu -1,4 jest zaokrąglane do -1, a od -1,6 do -2, a zero nie eksploduje.

GNIĆ
źródło
4
... ale nie udaje się na liczbach ujemnych: jeszcze lepszy sprint
alessandro
2
Ach nie, nie ma. Zaokrąglenie liczby ujemnej przybliża cię do zera, a nie dalej. Czego teraz uczą w szkołach?
RET
6
@RET Tak, nie udaje się z liczbami ujemnymi. $ float = -1,4 daje w wyniku 0 przy tej metodzie. Nie tego uczyli w mojej szkole. Pamiętaj, że int () obcina w kierunku zera.
fishinear
4
@fishinear Masz rację i jestem należycie karany. Ale powiedziałem „w trywialnym przypadku użycia”. Moja odpowiedź została poprawiona.
RET
1
Zauważ, że $ float = 0, to się nie powiedzie :-)
mat
74

Możesz użyć modułu takiego jak Math :: Round :

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

Lub możesz to zrobić w brutalny sposób:

my $rounded = sprintf "%.0f", $float;
EvdB
źródło
46

Jeśli zdecydujesz się użyć printf lub sprintf, pamiętaj, że używają one metody 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
źródło
Dziękuję za zwrócenie uwagi. Dokładniej, nazwa metody to „Round Half to Even”.
Jean Vincent,
Wszystkie odpowiedzi, które wspominają o printf lub sprintf, powinny o tym wspomnieć.
szalony
To niezwykle ważna informacja. Miałem kilka błędów w oprogramowaniu, ponieważ zakładałem, że 5 będzie zawsze zaokrąglane w górę. W końcu odkryłem, dlaczego Perl nigdy nie zrobił tego, co chciałem. Dziękuję za zwrócenie uwagi.
Boris Däppen,
W rzeczywistości jest to zależne od systemu operacyjnego! W Windows zaokrągli połowę od zera, a unix-like zaokrągli połowę do równej: exploringbinary.com/…
Apoc
9

Zobacz perldoc / perlfaq :

Pamiętaj, że int()po prostu skraca się w kierunku 0. Zaokrągla do określonej liczby cyfr sprintf()lub printf()jest zwykle najłatwiejszą trasą.

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

POSIXModuł (część standardowego rozkładu Perl) narzędzia ceil(), floor()oraz szereg innych funkcji matematycznych i trygonometrycznych.

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

Od 5000 do 5,003 perlów trygonometria została wykonana w Math::Complexmodule.

W 5.004 Math::Trigmoduł (część standardowej dystrybucji Perla)> implementuje funkcje trygonometryczne.

Wewnętrznie wykorzystuje Math::Complexmoduł, a niektóre funkcje mogą wyłamać się z rzeczywistej osi na płaszczyznę zespoloną, na przykład odwrotny sinus 2.

Zaokrąglanie wniosków finansowych może mieć poważne konsekwencje, dlatego należy dokładnie określić zastosowaną metodę zaokrąglania. W takich przypadkach prawdopodobnie opłaca się nie ufać jakimkolwiek zaokrągleniom systemowym używanym przez Perl, ale zamiast tego zaimplementować funkcję zaokrąglania, której potrzebujesz.

Aby zobaczyć, dlaczego, zwróć uwagę, że nadal będziesz mieć problem z przemianą w połowie punktu:

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

Nie wiń Perla. Jest tak samo jak w C. IEEE mówi, że musimy to zrobić. Liczby Perla, których wartości bezwzględne są liczbami całkowitymi poniżej 2 ** 31 (na komputerach 32-bitowych), będą działać podobnie jak matematyczne liczby całkowite. Inne numery nie są gwarantowane.

Kent Fredric
źródło
3

Nie potrzebujesz żadnego zewnętrznego modułu.

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

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

Być może nie rozumiem, ale pomyślałem, że to znacznie czystszy sposób na wykonanie tej samej pracy.

To co robi to przejście przez każdą liczbę dodatnią w elemencie, wypisanie liczby i zaokrąglonej liczby całkowitej we wspomnianym formacie. Kod łączy odpowiednie zaokrąglone dodatnie liczby całkowite tylko na podstawie liczb dziesiętnych. int ($ _) zasadniczo zaokrągla liczbę w dół, więc ($ -int ($ )) przechwytuje ułamki dziesiętne. Jeśli liczby dziesiętne są (z definicji) dokładnie mniejsze niż 0,5, zaokrąglij liczbę w dół. Jeśli nie, zaokrąglij w górę, dodając 1.

activealexaoki
źródło
1
Po raz kolejny, po co odpowiadać na starożytne pytanie skomplikowaną odpowiedzią, skoro coś w rodzaju odpowiedzi RET działa równie dobrze.
Joel Berger
1
To naprawdę nie jest zbyt skomplikowane, a odpowiedź RET zawiera sporo matematyki, która a) teoretycznie grozi przepełnieniem, b) zajmuje więcej czasu, c) niepotrzebnie wprowadza większą nieprecyzyjność fp do ostatecznej wartości. Zaraz, który znowu jest skomplikowany? ;)
cptstubing06
2

Poniższe działania zaokrągla liczby dodatnie lub ujemne do podanej pozycji dziesiętnej:

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

    return (int($x / $a + (($x < 0) ? -0.5 : 0.5)) * $a);
}
seacoder
źródło
1

Poniżej znajduje się próbka pięciu różnych sposobów sumowania wartości. Pierwsza to naiwny sposób wykonania sumowania (i zawodzi). Druga próba użycia sprintf(), ale też się nie udaje. Trzeci używa sprintf()pomyślnie, podczas gdy ostatnie dwa (czwarte i piąte) używają 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

Zwróć uwagę, że floor($value + 0.5)można go zastąpić, int($value + 0.5)aby usunąć zależność od POSIX.

David Beckman
źródło
1

Liczby ujemne mogą dodawać pewne dziwactwa, o których ludzie muszą wiedzieć.

printfPodejścia w stylu dają nam prawidłowe liczby, ale mogą powodować dziwne wyświetlanie. Odkryliśmy, że ta metoda (moim zdaniem głupio) stawia -znak, czy powinna, czy nie. Na przykład, -0,01 zaokrąglone do jednego miejsca po przecinku zwraca -0,0, a nie tylko 0. Jeśli masz zamiar zastosować printfpodejście stylowe i wiesz, że nie chcesz, użyj %di nie %f(jeśli potrzebujesz miejsc po przecinku, wtedy wyświetlacz staje się niewyraźny).

Chociaż jest poprawny i dla matematyki nic wielkiego, w przypadku wyświetlania wygląda po prostu dziwnie, pokazując coś w rodzaju „-0,0”.

W przypadku metody int liczby ujemne mogą w rezultacie zmienić to, co chcesz (chociaż istnieje kilka argumentów, które można uczynić, że są poprawne).

int + 0.5Powoduje rzeczywiste problemy z numerami -wykluczające, chyba że chcesz to działa w ten sposób, ale wyobrażam sobie, że większość ludzi nie. -0,9 powinno prawdopodobnie zaokrąglić do -1, a nie 0. Jeśli wiesz, że chcesz, aby wartość ujemna była sufitem, a nie podłogą, możesz to zrobić w jednej linii, w przeciwnym razie możesz chcieć użyć metody int z drugorzędnym modyfikacja (to oczywiście działa tylko w celu odzyskania liczb całkowitych:

my $var = -9.1;
my $tmpRounded = int( abs($var) + 0.5));
my $finalRounded = $var >= 0 ? 0 + $tmpRounded : 0 - $tmpRounded;
matowe
źródło
0

Moje rozwiązanie dla 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
źródło
0

Jeśli zależy ci tylko na uzyskaniu wartości całkowitej z całej liczby zmiennoprzecinkowej (tj. 12347,9999 lub 54321,0001), to podejście (pożyczone i zmodyfikowane z góry) załatwi sprawę:

my $rounded = floor($float + 0.1); 
HoldOffHunger
źródło
0

mnóstwo dokumentacji na temat zaokrąglania liczb, wielu ekspertów sugeruje pisanie własnych procedur zaokrąglania, ponieważ wersja „gotowa” dostarczona z Twoim językiem może nie być wystarczająco dokładna lub zawierać błędy. wyobrażam sobie jednak, że mówią o wielu miejscach po przecinku, a nie tylko o jednym, dwóch lub trzech. mając to na uwadze, oto moje rozwiązanie (chociaż nie DOKŁADNIE zgodnie z wymaganiami, ponieważ moje potrzeby to wyświetlanie dolarów - proces nie różni się jednak zbytnio).

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
źródło
aby podprogram był zgodny z twoimi specyfikacjami, po prostu zmodyfikuj następujące: if ($digit3 >= 5) { $digit3 = 0; $digit2++; if ($digit2 > 9) { $digit2 = 0; $digit1++; if ($digit1 > 9) { $digit1 = 0; $cost[0]++; } } } więc jest: if ($digit1 >= 5) { $digit1 = 0; $cost[0]++; } wtedy po prostureturn 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
źródło