Comment arrondir un nombre à virgule flottante en Perl?

174

Comment arrondir un nombre décimal (virgule flottante) à l'entier le plus proche?

par exemple

1.2 = 1
1.7 = 2
Ranguard
la source

Réponses:

196

Sortie de perldoc -q round

Perl a-t-il une fonction round ()? Qu'en est-il de ceil () et floor ()? Fonctions de déclenchement?

Rappelez-vous que cela int()tronque simplement vers 0. Pour arrondir à un certain nombre de chiffres, sprintf()ou printf()est généralement l'itinéraire le plus simple.

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

Le POSIXmodule (partie de la distribution standard de Perl) met en œuvre ceil(), floor()et un certain nombre d'autres fonctions mathématiques et trigonométriques.

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

Dans 5.000 à 5.003 perls, la trigonométrie a été effectuée dans le Math::Complex module. Avec 5.004, le Math::Trigmodule (qui fait partie de la distribution standard de Perl) implémente les fonctions trigonométriques. En interne, il utilise le Math::Complexmodule et certaines fonctions peuvent sortir de l'axe réel dans le plan complexe, par exemple le sinus inverse de 2.

L'arrondi dans les applications financières peut avoir de graves implications et la méthode d'arrondi utilisée doit être spécifiée avec précision. Dans ces cas, il vaut probablement mieux ne pas faire confiance à l'arrondi système utilisé par Perl, mais plutôt implémenter la fonction d'arrondi dont vous avez besoin.

Pour voir pourquoi, remarquez que vous aurez toujours un problème sur l'alternance à mi-chemin:

    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

Ne blâmez pas Perl. C'est la même chose que dans C. IEEE dit que nous devons le faire. Les nombres Perl dont les valeurs absolues sont des entiers inférieurs 2**31(sur les machines 32 bits) fonctionneront à peu près comme des entiers mathématiques. Les autres numéros ne sont pas garantis.

Vinko Vrsalovic
la source
17
^ Thariama, pourquoi ceil serait-il obsolète? Ce n'est pas obsolète dans POSIX ou perl pour autant que je sache. Citation requise!
Sam Watkins
3
@Beginners, n'essayez pas d'utiliser printfsi vous voulez le résultat dans une variable, utilisez sprintf... j'espère que cela vous fera gagner du temps de débogage :-P
Boris Däppen
Puis-je utiliser int()sur les PDL?
CinCout
1
utilisez POSIX; <br/> $ x = ($ x - plancher ($ x)> = .5)? ceil (x $): plancher (x $);
Joseph Argenio
136

Sans être en désaccord avec les réponses complexes sur les notes à mi-chemin et ainsi de suite, pour le cas d'utilisation le plus courant (et peut-être trivial):

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

METTRE À JOUR

S'il est possible que vous $floatsoyez négatif, la variation suivante produira le résultat correct:

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

Avec ce calcul, -1,4 est arrondi à -1 et -1,6 à -2, et zéro n'explosera pas.

RET
la source
4
... mais ça échoue sur les nombres négatifs: encore meilleur sprintf
alessandro
2
Ah non, ce n'est pas le cas. Arrondir un nombre négatif vous rapproche de zéro, pas plus loin. Qu'est-ce qu'ils enseignent dans les écoles ces jours-ci?
RET
6
@RET Oui, cela échoue avec des nombres négatifs. $ float = -1,4 donne 0 avec cette méthode. Ce n'est pas ce qu'ils ont enseigné à mon école. N'oubliez pas que int () tronque vers zéro.
fishinear
4
@fishinear Vous avez raison, et je suis dûment châtié. Mais j'ai dit «pour un cas d'utilisation trivial». Ma réponse a été corrigée.
RET
1
Notez que $ float = 0, cela échouera :-)
mat
74

Vous pouvez soit utiliser un module comme Math :: Round :

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

Ou vous pouvez le faire de manière grossière:

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

Si vous décidez d'utiliser printf ou sprintf, notez qu'ils utilisent la méthode 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
la source
Merci de l'avoir signalé. Plus précisément, le nom de la méthode est «Round Half to Even».
Jean Vincent
Toutes les réponses qui mentionnent printf ou sprintf devraient le mentionner.
insaner
C'est une information extrêmement importante. J'ai eu plusieurs bogues dans le logiciel parce que j'ai supposé que 5 seront toujours arrondis. J'ai finalement trouvé pourquoi perl n'a jamais fait ce que je voulais. Merci de l'avoir signalé.
Boris Däppen
En fait, cela dépend du système d'exploitation! Dans Windows , il arrondit demi de zéro et unix volonté un demi - tour à même: exploringbinary.com/...
Apoc
9

Voir perldoc / perlfaq :

N'oubliez pas que int()tronque simplement vers 0. Pour arrondir à un certain nombre de chiffres, sprintf()ou printf()est généralement l'itinéraire le plus simple.

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

Le POSIXmodule (partie de la distribution standard de Perl) met en œuvre ceil(), floor()et un certain nombre d'autres fonctions mathématiques et trigonométriques.

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

Dans 5.000 à 5.003 perls, la trigonométrie a été effectuée dans le Math::Complexmodule.

Avec 5.004, le Math::Trigmodule (qui fait partie de la distribution Perl standard)> implémente les fonctions trigonométriques.

En interne, il utilise le Math::Complexmodule et certaines fonctions peuvent sortir de l'axe réel dans le plan complexe, par exemple le sinus inverse de 2.

L'arrondi dans les applications financières peut avoir de graves implications et la méthode d'arrondi utilisée doit être spécifiée avec précision. Dans ces cas, il vaut probablement mieux ne pas faire confiance à l'arrondi système utilisé par Perl, mais plutôt implémenter la fonction d'arrondi dont vous avez besoin.

Pour voir pourquoi, remarquez que vous aurez toujours un problème sur l'alternance à mi-chemin:

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

Ne blâmez pas Perl. C'est la même chose que dans C. IEEE dit que nous devons le faire. Les nombres Perl dont les valeurs absolues sont des entiers inférieurs à 2 ** 31 (sur les machines 32 bits) fonctionneront à peu près comme des entiers mathématiques. Les autres numéros ne sont pas garantis.

Kent Fredric
la source
3

Vous n'avez besoin d'aucun module externe.

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

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

Votre argument me manque peut-être, mais je pensais que c'était une manière beaucoup plus propre de faire le même travail.

Cela permet de parcourir chaque nombre positif de l'élément, d'imprimer le nombre et l'entier arrondi dans le format que vous avez mentionné. Le code concatène un entier positif arrondi respectif uniquement en fonction des décimales. int ($ _) essentiellement rond vers le bas le nombre si ($ -INT ($ )) capture les décimales. Si les décimales sont (par définition) strictement inférieures à 0,5, arrondissez le nombre à la baisse. Sinon, arrondissez en ajoutant 1.

activealexaoki
la source
1
Encore une fois, pourquoi répondre à une question ancienne avec une réponse compliquée alors que quelque chose comme la réponse de RET fonctionne aussi bien.
Joel Berger
1
Ce n'est vraiment pas très compliqué, et la réponse de RET implique un tas de mathématiques qui a) risque théoriquement de déborder, b) prend plus de temps et c) introduit inutilement plus d'imprécision fp à votre valeur finale. Attendez, lequel est encore compliqué? ;)
cptstubing06
2

Ce qui suit arrondira les nombres positifs ou négatifs à une position décimale donnée:

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

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

Voici un exemple de cinq façons différentes de sommer les valeurs. Le premier est une manière naïve d'effectuer la sommation (et échoue). La seconde tente d'utiliser sprintf(), mais elle échoue aussi. Le troisième utilise sprintf()avec succès tandis que les deux derniers (4e et 5e) utilisent 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

Notez que floor($value + 0.5)peut être remplacé par int($value + 0.5)pour supprimer la dépendance sur POSIX.

David Beckman
la source
1

Les nombres négatifs peuvent ajouter des bizarreries dont les gens doivent être conscients.

printf-Les approches de style nous donnent des nombres corrects, mais elles peuvent entraîner des affichages étranges. Nous avons découvert que cette méthode (à mon avis, bêtement) met un -signe si oui ou non elle devrait ou non. Par exemple, -0,01 arrondi à une décimale renvoie un -0,0, plutôt que juste 0. Si vous allez faire l' printfapproche de style, et que vous savez que vous ne voulez pas de décimale, utilisez %det non %f(lorsque vous avez besoin de décimales, c'est quand le l'affichage devient bancal).

Bien que ce soit correct et pour les mathématiques, ce n'est pas un problème, pour l'affichage, cela semble bizarre en montrant quelque chose comme "-0.0".

Pour la méthode int, les nombres négatifs peuvent changer ce que vous voulez en conséquence (bien que certains arguments puissent être avancés, ils sont corrects).

La int + 0.5cause des problèmes réels avec des numéros séronégatifs, sauf si vous voulez que cela fonctionne de cette façon, mais j'imagine que la plupart des gens ne le font pas. -0,9 devrait probablement arrondir à -1, pas à 0. Si vous savez que vous voulez que le négatif soit un plafond plutôt qu'un plancher, vous pouvez le faire en une seule ligne, sinon, vous voudrez peut-être utiliser la méthode int avec un mineur modification (cela ne fonctionne évidemment que pour récupérer des nombres entiers:

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

Ma solution pour 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
la source
0

Si vous ne souhaitez obtenir qu'une valeur entière à partir d'un nombre entier à virgule flottante (c'est-à-dire 12347,9999 ou 54321,0001), cette approche (empruntée et modifiée par le haut) fera l'affaire:

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

beaucoup de documentation de lecture sur la façon d'arrondir les nombres, de nombreux experts suggèrent d'écrire vos propres routines d'arrondi, car la version «en conserve» fournie avec votre langue peut ne pas être assez précise ou contenir des erreurs. J'imagine, cependant, qu'ils parlent de nombreuses décimales, pas seulement un, deux ou trois. dans cet esprit, voici ma solution (bien que pas EXACTEMENT aussi demandé que mes besoins sont d'afficher des dollars - le processus n'est pas très différent, cependant).

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
la source
pour rendre le sous-programme conforme à vos spécifications, modifiez simplement ce qui suit: if ($digit3 >= 5) { $digit3 = 0; $digit2++; if ($digit2 > 9) { $digit2 = 0; $digit1++; if ($digit1 > 9) { $digit1 = 0; $cost[0]++; } } } donc c'est: if ($digit1 >= 5) { $digit1 = 0; $cost[0]++; } alors justereturn 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
la source