Wie rundet man eine Gleitkommazahl in Perl?

174

Wie kann ich eine Dezimalzahl (Gleitkomma) auf die nächste Ganzzahl runden?

z.B

1.2 = 1
1.7 = 2
Ranguard
quelle

Antworten:

196

Ausgabe von perldoc -q round

Hat Perl eine round () Funktion? Was ist mit Decke () und Boden ()? Triggerfunktionen?

Denken Sie daran, dass int()nur in Richtung abgeschnitten 0. Zum Runden auf eine bestimmte Anzahl von Stellen sprintf()oder printf()ist in der Regel die einfachste Route.

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

Das POSIXModul (Teil der Standard - Perl - Verteilung) implementiert ceil(), floor()und eine Reihe von anderen mathematischen und trigonometrischen Funktionen.

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

Bei 5.000 bis 5.003 Perlen wurde im Math::Complex Modul eine Trigonometrie durchgeführt . Mit 5.004 Math::Trigimplementiert das Modul (Teil der Standard-Perl-Distribution) die trigonometrischen Funktionen. Intern wird das Math::ComplexModul verwendet und einige Funktionen können von der realen Achse in die komplexe Ebene ausbrechen, beispielsweise der inverse Sinus von 2.

Rundungen in Finanzanwendungen können schwerwiegende Folgen haben, und die verwendete Rundungsmethode sollte genau angegeben werden. In diesen Fällen lohnt es sich wahrscheinlich, nicht der von Perl verwendeten Systemrundung zu vertrauen, sondern stattdessen die von Ihnen benötigte Rundungsfunktion selbst zu implementieren.

Um zu sehen, warum, beachten Sie, dass Sie immer noch ein Problem mit dem Wechsel auf halber Strecke haben:

    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

Beschuldige Perl nicht. Es ist das gleiche wie in C. IEEE sagt, dass wir dies tun müssen. Perl-Zahlen, deren absolute Werte Ganzzahlen unter 2**31(auf 32-Bit-Computern) sind, funktionieren ähnlich wie mathematische Ganzzahlen. Andere Nummern sind nicht garantiert.

Vinko Vrsalovic
quelle
17
^ Thariama, warum sollte Ceil veraltet sein? Soweit ich weiß, ist es in POSIX oder Perl nicht veraltet. Zitat benötigt!
Sam Watkins
3
@ Anfänger, versuchen printfSie nicht zu verwenden, wenn Sie das Ergebnis in einer Variablen wollen, verwenden Sie sprintf... hoffe, dies spart Ihnen einige Debugging-Zeit :-P
Boris Däppen
Kann ich int()auf PDLs verwenden?
CinCout
1
benutze POSIX; <br/> $ x = ($ x - Etage ($ x)> = .5)? Decke ($ x): Boden ($ x);
Joseph Argenio
136

Obwohl Sie den komplexen Antworten zu Halbwertszeiten usw. nicht widersprechen, gilt für den allgemeineren (und möglicherweise trivialeren) Anwendungsfall Folgendes:

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

AKTUALISIEREN

Wenn es möglich ist, dass Sie $floatnegativ sind, führt die folgende Variation zum richtigen Ergebnis:

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

Bei dieser Berechnung wird -1,4 auf -1 und -1,6 auf -2 gerundet, und Null explodiert nicht.

RET
quelle
4
... aber es funktioniert nicht auf negative Zahlen: noch besser sprintf
alessandro
2
Ah nein, das tut es nicht. Wenn Sie eine negative Zahl aufrunden, kommen Sie näher an Null und nicht weiter entfernt. Was unterrichten sie heutzutage in Schulen?
RET
6
@ RET Ja, es schlägt mit negativen Zahlen fehl. $ float = -1,4 ergibt bei dieser Methode 0. Das haben sie an meiner Schule nicht unterrichtet. Denken Sie daran, dass int () gegen Null abschneidet.
fishinear
4
@fishinear Du bist richtig, und ich bin gebührend gezüchtigt. Aber ich habe 'für einen trivialen Anwendungsfall' gesagt. Meine Antwort wurde korrigiert.
RET
1
Beachten Sie, dass es $ float = 0 ist, dies wird fehlschlagen :-)
mat
74

Sie können entweder ein Modul wie Math :: Round verwenden :

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

Oder Sie können es auf grobe Weise tun:

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

Wenn Sie sich für printf oder sprintf entscheiden, beachten Sie, dass die Methode Round Round to Even verwendet wird.

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
quelle
Vielen Dank für den Hinweis. Genauer gesagt lautet der Name der Methode "Round Half to Even".
Jean Vincent
Alle Antworten, die printf oder sprintf erwähnen, sollten dies erwähnen.
Wahnsinniger
Dies ist eine äußerst wichtige Information. Ich hatte mehrere Fehler in der Software, weil ich davon ausgegangen bin, dass 5 immer aufgerundet werden. Ich fand schließlich heraus, warum Perl nie tat, was ich wollte. Vielen Dank für den Hinweis.
Boris Däppen
Eigentlich ist das OS abhängig! In Windows wird es zur Hälfte von Null abgerundet und Unix-ähnlich wird zur Hälfte gerundet: Exploringbinary.com/…
Apoc
9

Siehe perldoc / perlfaq :

Denken Sie daran, dass dies int()nur in Richtung 0 abgeschnitten wird. Zum Runden auf eine bestimmte Anzahl von Ziffern sprintf()oder printf()normalerweise die einfachste Route.

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

Das POSIXModul (Teil der Standard - Perl - Verteilung) implementiert ceil(), floor()und eine Reihe von anderen mathematischen und trigonometrischen Funktionen.

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

Bei 5.000 bis 5.003 Perlen wurde im Math::ComplexModul eine Trigonometrie durchgeführt .

Mit 5.004 Math::Trigimplementiert das Modul (Teil der Standard-Perl-Distribution)> die trigonometrischen Funktionen.

Intern wird das Math::ComplexModul verwendet und einige Funktionen können von der realen Achse in die komplexe Ebene ausbrechen, beispielsweise der inverse Sinus von 2.

Rundungen in Finanzanwendungen können schwerwiegende Folgen haben, und die verwendete Rundungsmethode sollte genau angegeben werden. In diesen Fällen lohnt es sich wahrscheinlich, nicht der von Perl verwendeten Systemrundung zu vertrauen, sondern stattdessen die von Ihnen benötigte Rundungsfunktion selbst zu implementieren.

Um zu sehen, warum, beachten Sie, dass Sie immer noch ein Problem mit dem Wechsel auf halber Strecke haben:

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

Beschuldige Perl nicht. Es ist das gleiche wie in C. IEEE sagt, wir müssen dies tun. Perl-Zahlen, deren absolute Werte Ganzzahlen unter 2 ** 31 sind (auf 32-Bit-Computern), funktionieren ähnlich wie mathematische Ganzzahlen. Andere Nummern sind nicht garantiert.

Kent Fredric
quelle
3

Sie benötigen kein externes Modul.

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

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

Ich vermisse vielleicht Ihren Standpunkt, aber ich dachte, dies wäre eine viel sauberere Art, den gleichen Job zu machen.

Dazu gehen Sie durch jede positive Zahl im Element, drucken die Zahl und die gerundete Ganzzahl in dem von Ihnen genannten Format. Der Code verkettet die jeweilige gerundete positive Ganzzahl nur basierend auf den Dezimalstellen. int ($ _) rundet die Zahl grundsätzlich ab, sodass ($ -int ($ )) die Dezimalstellen erfasst. Wenn die Dezimalstellen (per Definition) streng kleiner als 0,5 sind, runden Sie die Zahl ab. Wenn nicht, runden Sie mit 1 auf.

activealexaoki
quelle
1
Warum noch einmal, warum sollte man eine alte Frage mit einer komplizierten Antwort beantworten, wenn so etwas wie die Antwort von RET genauso gut funktioniert?
Joel Berger
1
Dies ist wirklich nicht sehr kompliziert, und die Antwort von RET beinhaltet eine Menge Mathematik, die a) theoretisch das Risiko eines Überlaufs birgt, b) länger dauert und c) unnötigerweise mehr fp-Ungenauigkeit in Ihren Endwert einführt. Warten Sie, welches ist wieder kompliziert? ;)
cptstubing06
2

Das Folgende rundet positive oder negative Zahlen auf eine bestimmte Dezimalstelle:

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

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

Im Folgenden finden Sie eine Auswahl von fünf verschiedenen Möglichkeiten zum Summieren von Werten. Der erste ist ein naiver Weg, um die Summierung durchzuführen (und schlägt fehl). Der zweite Versuch zu verwendensprintf() , aber es schlägt auch fehl. Der dritte wird sprintf()erfolgreich verwendet, während die letzten beiden (4. und 5.) verwendet werden 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

Beachten Sie, dass floor($value + 0.5)durch ersetzt werden kann int($value + 0.5), um die Abhängigkeit von zu entfernen POSIX.

David Beckman
quelle
1

Negative Zahlen können einige Macken hinzufügen, die die Leute beachten müssen.

printfAnsätze im Stil geben uns korrekte Zahlen, aber sie können zu ungeraden Anzeigen führen. Wir haben herausgefunden, dass diese Methode (meiner Meinung nach dumm) ein -Zeichen setzt , ob sie sollte oder nicht. Zum Beispiel gibt -0,01 auf eine Dezimalstelle gerundet eine -0,0 statt nur 0 zurück. Wenn Sie den Stilansatz ausführen printfmöchten und wissen, dass Sie keine Dezimalstelle möchten, verwenden Sie %dund nicht %f(wenn Sie Dezimalstellen benötigen, ist es, wenn die Anzeige wird wackelig).

Während es korrekt ist und für Mathematik keine große Sache ist, sieht es für die Anzeige nur seltsam aus, wenn man so etwas wie "-0.0" zeigt.

Bei der int-Methode können negative Zahlen das ändern, was Sie als Ergebnis wollen (obwohl einige Argumente vorgebracht werden können, sind sie korrekt).

Das int + 0.5 verursacht echte Probleme mit -negativen Zahlen, es sei denn, Sie möchten, dass es so funktioniert, aber ich kann mir vorstellen, dass die meisten Menschen dies nicht tun. -0,9 sollte wahrscheinlich auf -1 und nicht auf 0 runden. Wenn Sie wissen, dass Negativ eher eine Decke als ein Boden sein soll, können Sie dies einzeilig tun. Andernfalls möchten Sie möglicherweise die int-Methode mit einem Moll verwenden Modifikation (dies funktioniert offensichtlich nur, um ganze Zahlen zurückzugewinnen:

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

Meine Lösung für 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
quelle
0

Wenn Sie nur einen ganzzahligen Wert aus einer ganzen Gleitkommazahl (dh 12347.9999 oder 54321.0001) erhalten möchten, reicht dieser Ansatz (von oben entlehnt und geändert) aus:

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

Viele Experten empfehlen, eigene Rundungsroutinen zu schreiben, da die mit Ihrer Sprache gelieferte "Dosen" -Version möglicherweise nicht präzise genug ist oder Fehler enthält. Ich stelle mir jedoch vor, dass sie viele Dezimalstellen sprechen, nicht nur eine, zwei oder drei. In diesem Sinne ist hier meine Lösung (obwohl nicht genau so gefordert, wie es meine Bedürfnisse sind, Dollars anzuzeigen - der Prozess ist jedoch nicht viel anders).

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
quelle
Um das Unterprogramm an Ihre Spezifikationen anzupassen, ändern Sie einfach Folgendes: if ($digit3 >= 5) { $digit3 = 0; $digit2++; if ($digit2 > 9) { $digit2 = 0; $digit1++; if ($digit1 > 9) { $digit1 = 0; $cost[0]++; } } } So ist es: if ($digit1 >= 5) { $digit1 = 0; $cost[0]++; } dann nurreturn 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
quelle