Come si arrotonda un numero in virgola mobile in Perl?


174

Come posso arrotondare un numero decimale (virgola mobile) al numero intero più vicino?

per esempio

1.2 = 1
1.7 = 2

Risposte:


196

Uscita di perldoc -q round

Perl ha una funzione round ()? Che dire di ceil () e floor ()? Funzioni Trig?

Ricorda che int()si tronca semplicemente verso 0. Per arrotondare a un certo numero di cifre, sprintf()o di printf()solito è il percorso più semplice.

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

Il POSIXmodulo (parte della distribuzione standard di Perl) implementa ceil(), floor()e una serie di altre funzioni matematiche e trigonometriche.

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

In 5.000 a 5.003 perl, la trigonometria è stata eseguita nel Math::Complex modulo. Con 5.004, il Math::Trigmodulo (parte della distribuzione standard Perl) implementa le funzioni trigonometriche. Internamente utilizza il Math::Complexmodulo e alcune funzioni possono passare dall'asse reale al piano complesso, ad esempio il seno inverso di 2.

L'arrotondamento nelle applicazioni finanziarie può avere serie implicazioni e il metodo di arrotondamento utilizzato deve essere specificato con precisione. In questi casi, probabilmente vale la pena non fidarsi di qualsiasi arrotondamento di sistema utilizzato da Perl, ma invece di implementare la funzione di arrotondamento necessaria.

Per capire perché, nota come avrai ancora un problema con l'alternanza a metà percorso:

    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

Non dare la colpa a Perl. È lo stesso di C. IEEE dice che dobbiamo farlo. I numeri Perl i cui valori assoluti sono numeri interi sotto 2**31(su macchine a 32 bit) funzioneranno praticamente come numeri matematici. Altri numeri non sono garantiti.


17
^ Thariama, perché il soffitto dovrebbe essere deprecato? Per quanto ne so, non è deprecato in POSIX o perl. Citazione necessaria!
Sam Watkins,

3
@Beginners, non provare a usare printfse vuoi il risultato in una variabile, usa sprintf... spero che questo ti risparmi un po 'di tempo di debug :-P
Boris Däppen

Posso usare int()su PDL?
CinCout,

1
usa POSIX; <br/> $ x = ($ x - piano ($ x)> = .5)? ceil ($ x): piano ($ x);
Joseph Argenio,

136

Pur non essendo in disaccordo con le risposte complesse sui marchi a metà strada e così via, per il caso d'uso più comune (e forse banale):

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

AGGIORNARE

Se è possibile che tu $floatsia negativo, la seguente variazione produrrà il risultato corretto:

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

Con questo calcolo -1.4 viene arrotondato a -1 e -1.6 a -2 e lo zero non esploderà.


4
... ma fallisce sui numeri negativi: sprintf ancora migliore
alessandro

2
Ah no, non lo fa. Arrotondare un numero negativo ti avvicina allo zero, non più lontano. Cosa insegnano nelle scuole in questi giorni?
RET

6
@RET Sì, non riesce con numeri negativi. $ float = -1.4 risulta in 0 con questo metodo. Non è quello che hanno insegnato nella mia scuola. Ricorda che int () tronca verso zero.
Fishinear del

4
@fishinear Hai ragione, e io sono debitamente castigato. Ma ho detto "per banale caso d'uso". La mia risposta è stata corretta
RET

1
Si noti che $ float = 0, questo fallirà :-)
mat

74

Puoi usare un modulo come Math :: Round :

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

Oppure puoi farlo nel modo rozzo:

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

46

Se decidi di usare printf o sprintf, nota che usano la metà arrotondata per uniformare il metodo.

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

Grazie per averlo segnalato. Più precisamente, il nome del metodo è "Round Half to Even".
Jean Vincent,

Tutte le risposte che menzionano printf o sprintf dovrebbero menzionarlo.
pazzo

Questa è un'informazione estremamente importante. Ho avuto dei bachi temporali nel software perché supponevo che 5 fossero sempre arrotondati. Alla fine ho scoperto, perché Perl non ha mai fatto quello che volevo. Grazie per averlo segnalato.
Boris Däppen l'

In realtà, questo dipende dal sistema operativo! In Windows arrotonderà la metà da zero e unix-like arrotonderà la metà a pari: exploringbinary.com/…
Apoc

9

Vedi perldoc / perlfaq :

Ricorda che int()si tronca semplicemente verso 0. Per arrotondare a un certo numero di cifre, sprintf()o di printf()solito è il percorso più semplice.

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

Il POSIXmodulo (parte della distribuzione standard di Perl) implementa ceil(), floor()e una serie di altre funzioni matematiche e trigonometriche.

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

In 5.000 a 5.003 perl, la trigonometria è stata eseguita nel Math::Complexmodulo.

Con 5.004, il Math::Trigmodulo (parte della distribuzione standard Perl)> implementa le funzioni trigonometriche.

Internamente utilizza il Math::Complexmodulo e alcune funzioni possono passare dall'asse reale al piano complesso, ad esempio il seno inverso di 2.

L'arrotondamento nelle applicazioni finanziarie può avere serie implicazioni e il metodo di arrotondamento utilizzato deve essere specificato con precisione. In questi casi, probabilmente vale la pena non fidarsi di qualsiasi arrotondamento di sistema utilizzato da Perl, ma invece di implementare la funzione di arrotondamento necessaria.

Per capire perché, nota come avrai ancora un problema con l'alternanza a metà percorso:

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

Non dare la colpa a Perl. È lo stesso di C. IEEE dice che dobbiamo farlo. I numeri Perl i cui valori assoluti sono numeri interi inferiori a 2 ** 31 (su macchine a 32 bit) funzioneranno più o meno come numeri interi matematici. Altri numeri non sono garantiti.


3

Non è necessario alcun modulo esterno.

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

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

Forse mi manca il punto, ma ho pensato che fosse un modo molto più pulito di fare lo stesso lavoro.

Ciò che fa è scorrere tutti i numeri positivi nell'elemento, stampare il numero e il numero intero arrotondato nel formato menzionato. Il codice concatena il rispettivo intero positivo arrotondato solo in base ai decimali. int ($ _) sostanzialmente arrotondano il numero in modo che ($ -int ($ )) acquisisca i decimali. Se i decimali sono (per definizione) strettamente inferiori a 0,5, arrotondare per eccesso il numero. In caso contrario, arrotondare per eccesso aggiungendo 1.


1
Ancora una volta, perché rispondere a una domanda antica con una risposta complicata quando qualcosa come la risposta di RET funziona ugualmente bene.
Joel Berger,

1
Questo in realtà non è molto complicato, e la risposta di RET implica un mucchio di matematica che a) teoricamente rischia di traboccare, b) impiega più tempo, e c) introduce inutilmente più imprecisioni di fp al tuo valore finale. Aspetta, quale è di nuovo complicato? ;)
cptstubing06

2

Quanto segue arrotonderà i numeri positivi o negativi a una determinata posizione decimale:

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

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

1

Di seguito è riportato un esempio di cinque diversi modi per sommare i valori. Il primo è un modo ingenuo per eseguire la somma (e non riesce). Il secondo tenta di utilizzare sprintf(), ma anche questo fallisce. Il terzo usa sprintf()con successo mentre gli ultimi due (4 ° e 5 °) usano 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

Si noti che floor($value + 0.5)può essere sostituito con int($value + 0.5)per rimuovere la dipendenza POSIX.


1

I numeri negativi possono aggiungere alcune stranezze di cui le persone devono essere consapevoli.

printfgli approcci in stile ci danno numeri corretti, ma possono comportare delle visualizzazioni dispari. Abbiamo scoperto che questo metodo (secondo me, stupidamente) mette un -segno se dovrebbe o non dovrebbe. Ad esempio, -0,01 arrotondato al primo decimale restituisce un -0,0, anziché solo 0. Se hai intenzione di seguire l' printfapproccio di stile e sai che non vuoi un decimale, usa %de no %f(quando hai bisogno di decimali, è quando il display diventa traballante).

Mentre è corretto e per la matematica non è un grosso problema, per la visualizzazione sembra strano mostrare qualcosa come "-0.0".

Per il metodo int, i numeri negativi possono cambiare ciò che si desidera di conseguenza (anche se ci sono alcuni argomenti che possono essere fatti, sono corretti).

La int + 0.5causa problemi reali con i numeri -negative, se non volete farlo funzionare in questo modo, ma immagino la maggior parte delle persone non lo fanno. -0.9 probabilmente dovrebbe arrotondare a -1, non a 0. Se sai che vuoi che il negativo sia un soffitto piuttosto che un pavimento, puoi farlo in una riga, altrimenti potresti voler usare il metodo int con un minore modifica (questo ovviamente funziona solo per recuperare numeri interi:

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

0

La mia soluzione per 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

Se ti preoccupi solo di ottenere un valore intero da un numero intero in virgola mobile (ovvero 12347.9999 o 54321.0001), questo approccio (preso in prestito e modificato dall'alto) farà il trucco:

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

0

un sacco di leggere la documentazione su come arrotondare i numeri, molti esperti suggeriscono di scrivere le proprie routine di arrotondamento, poiché la versione "in scatola" fornita con la propria lingua potrebbe non essere abbastanza precisa o contenere errori. immagino, tuttavia, stiano parlando di molti decimali non solo uno, due o tre. con questo in mente, ecco la mia soluzione (sebbene non ESATTAMENTE come richiesto in quanto le mie esigenze sono di visualizzare dollari - il processo non è molto diverso, però).

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

per rendere la subroutine conforme alle tue specifiche, modifica semplicemente quanto segue: if ($digit3 >= 5) { $digit3 = 0; $digit2++; if ($digit2 > 9) { $digit2 = 0; $digit1++; if ($digit1 > 9) { $digit1 = 0; $cost[0]++; } } } quindi è: if ($digit1 >= 5) { $digit1 = 0; $cost[0]++; } quindi soloreturn commafied($cost[0]);
Jarett Lloyd

-2
cat table |
  perl -ne '/\d+\s+(\d+)\s+(\S+)/ && print "".**int**(log($1)/log(2))."\t$2\n";' 
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.