¿Cómo redondeas un número de coma flotante en Perl?


Respuestas:


196

Salida de perldoc -q round

¿Perl tiene una función round ()? ¿Qué pasa con ceil () y floor ()? ¿Funciones de disparo?

Recuerde que int()simplemente se trunca hacia 0. Para redondear a un cierto número de dígitos, sprintf()o printf()suele ser la ruta más fácil.

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

El POSIXmódulo (parte de la distribución estándar de Perl) implementos ceil(), floor()y una serie de otras funciones matemáticas y trigonométricas.

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

En 5.000 a 5.003 perls, la trigonometría se realizó en el Math::Complex módulo. Con 5.004, el Math::Trigmódulo (parte de la distribución estándar de Perl) implementa las funciones trigonométricas. Internamente usa el Math::Complexmódulo y algunas funciones pueden salir del eje real al plano complejo, por ejemplo, el seno inverso de 2.

El redondeo en las aplicaciones financieras puede tener serias implicaciones, y el método de redondeo utilizado debe especificarse con precisión. En estos casos, probablemente valga la pena no confiar en el sistema de redondeo que utiliza Perl, sino implementar la función de redondeo que usted necesita.

Para ver por qué, observe cómo todavía tendrá un problema con la alternancia de punto medio:

    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

No culpes a Perl. Es lo mismo que en C. IEEE dice que tenemos que hacer esto. Los números de Perl cuyos valores absolutos son enteros debajo 2**31(en máquinas de 32 bits) funcionarán más o menos como enteros matemáticos. Otros números no están garantizados.


17
^ Thariama, ¿por qué el techo sería desaprobado? No está en desuso en POSIX o perl, que yo sepa. Cita necesaria!
Sam Watkins el

3
@Beginners, no intente usar printfsi desea el resultado en una variable, use sprintf... espero que esto le ahorre algo de tiempo de depuración
:-P

¿Puedo usar int()en PDL?
CinCout

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

136

Si bien no está en desacuerdo con las respuestas complejas sobre las marcas a mitad de camino, etc., para el caso de uso más común (y posiblemente trivial):

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

ACTUALIZAR

Si es posible $floatque sea negativo, la siguiente variación producirá el resultado correcto:

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

Con este cálculo, -1.4 se redondea a -1 y -1.6 a -2, y el cero no explotará.


44
... pero falla en números negativos: aún mejor sprintf
alessandro

2
Ah no, no lo hace. Redondear un número negativo te lleva más cerca de cero, no más lejos. ¿Qué están enseñando en las escuelas en estos días?
RET

66
@RET Sí, falla con números negativos. $ float = -1.4 resulta en 0 con este método. Eso no es lo que enseñaron en mi escuela. Recuerde que int () se trunca hacia cero.
fishinear

44
@fishinear Tienes razón, y estoy debidamente castigado. Pero sí dije 'para casos de uso triviales'. Mi respuesta ha sido corregida.
RET

1
Tenga en cuenta que $ float = 0, esto fallará :-)
mat

74

Puedes usar un módulo como Math :: Round :

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

O puedes hacerlo de la manera cruda:

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

46

Si decide usar printf o sprintf, tenga en cuenta que usan la mitad redonda para igualar el método.

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

Gracias por señalar esto. Más precisamente, el nombre del método es 'Round Half to Even'.
Jean Vincent

Todas las respuestas que mencionan printf o sprintf deberían mencionar esto.
demente

Esta es una información extremadamente importante. Tuve varios errores en el software porque supuse que 5 siempre se redondearán. Finalmente descubrí por qué Perl nunca hizo lo que quería. Gracias por señalar esto.
Boris Däppen

En realidad, esto depende del sistema operativo. En Windows se redondeará a la mitad desde cero y unix se redondeará a la mitad para incluso: explorebinary.com/…
Apoc

9

Ver perldoc / perlfaq :

Recuerde que int()simplemente se trunca hacia 0. Para redondear a un cierto número de dígitos, sprintf()o printf()suele ser la ruta más fácil.

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

El POSIXmódulo (parte de la distribución estándar de Perl) implementos ceil(), floor()y una serie de otras funciones matemáticas y trigonométricas.

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

En 5.000 a 5.003 perls, la trigonometría se realizó en el Math::Complexmódulo.

Con 5.004, el Math::Trigmódulo (parte de la distribución estándar de Perl)> implementa las funciones trigonométricas.

Internamente usa el Math::Complexmódulo y algunas funciones pueden salir del eje real al plano complejo, por ejemplo, el seno inverso de 2.

El redondeo en las aplicaciones financieras puede tener serias implicaciones, y el método de redondeo utilizado debe especificarse con precisión. En estos casos, probablemente valga la pena no confiar en el sistema de redondeo que utiliza Perl, sino implementar la función de redondeo que usted necesita.

Para ver por qué, observe cómo todavía tendrá un problema con la alternancia de punto medio:

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

No culpes a Perl. Es lo mismo que en C. IEEE dice que tenemos que hacer esto. Los números de Perl cuyos valores absolutos son enteros menores de 2 ** 31 (en máquinas de 32 bits) funcionarán más o menos como enteros matemáticos. Otros números no están garantizados.


3

No necesitas ningún módulo externo.

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

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

Puede que me esté perdiendo su punto, pero pensé que esta era una forma mucho más limpia de hacer el mismo trabajo.

Lo que esto hace es recorrer cada número positivo en el elemento, imprimir el número y el entero redondeado en el formato que mencionó. El código concatena el entero positivo redondeado respectivo solo en función de los decimales. int ($ _) básicamente redondea el número para que ($ -int ($ )) capture los decimales. Si los decimales son (por definición) estrictamente menores que 0.5, redondee el número hacia abajo. Si no, redondee agregando 1.


1
Una vez más, ¿por qué responder una pregunta antigua con una respuesta complicada cuando algo como la respuesta de RET funciona igualmente bien?
Joel Berger

1
Esto realmente no es muy complicado, y la respuesta de RET involucra un montón de matemáticas que a) teóricamente arriesga el desbordamiento, b) lleva más tiempo yc) innecesariamente introduce más imprecisión fp en su valor final. Espera, ¿cuál es complicado otra vez? ;)
cptstubing06

2

Lo siguiente redondeará los números positivos o negativos a una posición decimal dada:

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

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

1

A continuación se muestra una muestra de cinco formas diferentes de sumar valores. La primera es una forma ingenua de realizar la suma (y falla). El segundo intenta usarlo sprintf(), pero también falla. El tercero usa con sprintf()éxito mientras que los dos últimos (4to y 5to) lo usan 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

Tenga en cuenta que floor($value + 0.5)se puede reemplazar con int($value + 0.5)para eliminar la dependencia POSIX.


1

Los números negativos pueden agregar algunas peculiaridades que las personas deben tener en cuenta.

printflos enfoques de estilo nos dan números correctos, pero pueden dar lugar a algunas pantallas extrañas. Hemos descubierto que este método (en mi opinión, estúpidamente) pone un -signo de si debería o no debería. Por ejemplo, -0.01 redondeado a una posición decimal devuelve un -0.0, en lugar de solo 0. Si va a hacer el printfenfoque de estilo y sabe que no quiere decimal, use %dy no %f(cuando necesita decimales, es cuando el la pantalla se torna inestable).

Si bien es correcto y para las matemáticas no es gran cosa, para mostrar simplemente se ve raro mostrando algo como "-0.0".

Para el método int, los números negativos pueden cambiar lo que desea como resultado (aunque hay algunos argumentos que pueden hacerse son correctos).

Esto int + 0.5causa problemas reales con los números negativos, a menos que quieras que funcione de esa manera, pero imagino que la mayoría de las personas no lo hacen. -0.9 probablemente debería redondearse a -1, no a 0. Si sabe que desea que lo negativo sea un techo en lugar de un piso, puede hacerlo de una sola línea, de lo contrario, es posible que desee utilizar el método int con un menor modificación (esto obviamente solo funciona para recuperar números enteros:

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

0

Mi solución para 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

Si solo le interesa obtener un valor entero de un número entero de coma flotante (es decir, 12347.9999 o 54321.0001), este enfoque (prestado y modificado desde arriba) hará el truco:

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

0

Muchos expertos sugieren escribir sus propias rutinas de redondeo, ya que la versión 'enlatada' proporcionada con su idioma puede no ser lo suficientemente precisa o contener errores. Sin embargo, imagino que están hablando muchos decimales, no solo uno, dos o tres. Con eso en mente, aquí está mi solución (aunque no EXACTAMENTE lo solicitado, ya que mis necesidades son mostrar dólares; sin embargo, el proceso no es muy diferente).

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

para hacer que la subrutina se ajuste a sus especificaciones, simplemente modifique lo siguiente: if ($digit3 >= 5) { $digit3 = 0; $digit2++; if ($digit2 > 9) { $digit2 = 0; $digit1++; if ($digit1 > 9) { $digit1 = 0; $cost[0]++; } } } así es: if ($digit1 >= 5) { $digit1 = 0; $cost[0]++; } entonces 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";' 
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.