Como você arredonda um número de ponto flutuante no Perl?

174

Como arredondar um número decimal (ponto flutuante) para o número inteiro mais próximo?

por exemplo

1.2 = 1
1.7 = 2
Ranguard
fonte

Respostas:

196

Saída de perldoc -q round

O Perl tem uma função round ()? E quanto ao teto () e piso ()? Trig funções?

Lembre-se de que int()apenas trunca 0. Para arredondar para um determinado número de dígitos, sprintf()ou printf()geralmente é a rota mais fácil.

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

O POSIXmódulo (parte do padrão de distribuição de Perl) implementa ceil(), floor()e um número de outras funções matemáticas e trigonométricas.

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

Em 5.000 a 5.003 perls, a trigonometria foi realizada no Math::Complex módulo. Com 5.004, o Math::Trigmódulo (parte da distribuição Perl padrão) implementa as funções trigonométricas. Internamente, ele usa o Math::Complexmódulo e algumas funções podem sair do eixo real para o plano complexo, por exemplo, o seno inverso de 2.

O arredondamento em aplicações financeiras pode ter sérias implicações, e o método de arredondamento usado deve ser especificado com precisão. Nesses casos, provavelmente vale a pena não confiar no arredondamento do sistema usado pelo Perl, mas implementar a função de arredondamento de que você precisa.

Para ver o porquê, observe como você ainda terá um problema na alternância de meio caminho:

    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

Não culpe Perl. É o mesmo que em C. O IEEE diz que precisamos fazer isso. Os números Perl cujos valores absolutos são inteiros abaixo 2**31(em máquinas de 32 bits) funcionarão praticamente como números matemáticos. Outros números não são garantidos.

Vinko Vrsalovic
fonte
17
^ Thariama, por que o teto seria depreciado? Não está obsoleto no POSIX ou perl, tanto quanto eu sei. Citação necessária!
31313 Sam Watkins
3
@Beginners, não tente usar printfse você quiser o resultado em uma variável, uso sprintf... espero que isso poupa-lhe algum tempo de depuração :-P
Boris Dappen
Posso usar int()em PDLs?
CinCout
1
use POSIX; <br/> $ x = ($ x - piso ($ x)> = 0,5)? teto ($ x): piso ($ x);
Joseph Argenio
136

Embora não discorde das respostas complexas sobre marcas de meio caminho e assim por diante, para o caso de uso mais comum (e possivelmente trivial):

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

ATUALIZAR

Se for possível que você $floatseja negativo, a seguinte variação produzirá o resultado correto:

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

Com esse cálculo, -1,4 é arredondado para -1 e -1,6 para -2, e zero não explode.

RET
fonte
4
... mas falha em números negativos: ainda melhor sprintf
alessandro
2
Ah não, não tem. Arredondar um número negativo leva você mais perto de zero, não mais longe. O que eles estão ensinando nas escolas hoje em dia?
RET
6
@RET Sim, falha com números negativos. $ float = -1.4 resulta em 0 com este método. Não foi isso que eles ensinaram na minha escola. Lembre-se de que int () trunca para zero.
fishinear perto de 20/12/12
4
@ fishinear Você está certo e estou devidamente castigado. Mas eu disse 'para casos de uso triviais'. Minha resposta foi corrigida.
RET
1
Observe que $ float = 0, isso falhará :-) #
mat #
74

Você pode usar um módulo como Math :: Round :

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

Ou você pode fazê-lo da maneira crua:

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

Se você decidir usar printf ou sprintf, observe que eles usam o método Metade redonda para o par .

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
fonte
Obrigado por apontar isso. Mais precisamente, o nome do método é 'Round Half to Even'.
Jean Vincent
Todas as respostas que mencionam printf ou sprintf devem mencionar isso.
Insaner #
Esta é uma informação extremamente importante. Eu tive erros de tempos de servidor no software porque eu assumi que 5 sempre serão arredondados. Finalmente descobri, por que o perl nunca fez o que queria. Obrigado por apontar isso.
Boris Däppen
Na verdade, isso depende do sistema operacional! No Windows ele vai rodada meia de distância de zero e unix-like vontade metade rodada até mesmo: exploringbinary.com/...
Apoc
9

Veja perldoc / perlfaq :

Lembre-se de que int()apenas trunca para 0. Para arredondar para um determinado número de dígitos, sprintf()ou printf()geralmente é a rota mais fácil.

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

O POSIXmódulo (parte do padrão de distribuição de Perl) implementa ceil(), floor()e um número de outras funções matemáticas e trigonométricas.

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

Em 5.000 a 5.003 perls, a trigonometria foi realizada no Math::Complexmódulo.

Com 5.004, o Math::Trigmódulo (parte da distribuição Perl padrão)> implementa as funções trigonométricas.

Internamente, ele usa o Math::Complexmódulo e algumas funções podem sair do eixo real para o plano complexo, por exemplo, o seno inverso de 2.

O arredondamento em aplicações financeiras pode ter sérias implicações, e o método de arredondamento usado deve ser especificado com precisão. Nesses casos, provavelmente vale a pena não confiar no arredondamento do sistema usado pelo Perl, mas implementar a função de arredondamento de que você precisa.

Para ver o porquê, observe como você ainda terá um problema na alternância de meio caminho:

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

Não culpe Perl. É o mesmo que em C. O IEEE diz que temos que fazer isso. Os números Perl cujos valores absolutos são números inteiros abaixo de 2 ** 31 (em máquinas de 32 bits) funcionarão praticamente como números matemáticos. Outros números não são garantidos.

Kent Fredric
fonte
3

Você não precisa de nenhum módulo externo.

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

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

Posso estar perdendo o seu argumento, mas achei que essa era uma maneira muito mais limpa de fazer o mesmo trabalho.

O que isso faz é percorrer todos os números positivos no elemento, imprimir o número e o número inteiro arredondado no formato que você mencionou. O código concatena o respectivo inteiro positivo arredondado apenas com base nos decimais. int ($ _) basicamente arredonda o número para que ($ -int ($ )) capture os decimais. Se os decimais forem (por definição) estritamente menores que 0,5, arredonde o número para baixo. Caso contrário, complete adicionando 1.

activealexaoki
fonte
1
Mais uma vez, por que responder a uma pergunta antiga com uma resposta complicada quando algo como a resposta do RET funciona igualmente bem.
Joel Berger
1
Isso realmente não é muito complicado, e a resposta do RET envolve um monte de matemática que: a) teoricamente corre o risco de transbordar; b) leva mais tempo; ec) introduz desnecessariamente mais imprecisão de FP ao seu valor final. Espere, qual é complicado de novo? ;)
cptstubing06
2

A seguir, serão arredondados números positivos ou negativos para uma determinada posição decimal:

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

    return (int($x / $a + (($x < 0) ? -0.5 : 0.5)) * $a);
}
codificador do mar
fonte
1

A seguir, é apresentada uma amostra de cinco maneiras diferentes de somar valores. A primeira é uma maneira ingênua de realizar a soma (e falha). A segunda tenta usar sprintf(), mas também falha. O terceiro usa com sprintf()sucesso, enquanto os dois finais (4º e 5º) usam 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

Observe que floor($value + 0.5)pode ser substituído por int($value + 0.5)para remover a dependência POSIX.

David Beckman
fonte
1

Os números negativos podem adicionar algumas peculiaridades que as pessoas precisam estar cientes.

printfabordagens de estilo nos dão números corretos, mas podem resultar em algumas exibições ímpares. Descobrimos que esse método (na minha opinião, estupidamente) coloca um -sinal de que deveria ou não deveria. Por exemplo, -0,01 arredondado para uma casa decimal retorna -0,0, em vez de apenas 0. Se você fizer a printfabordagem de estilo e souber que não deseja decimal, use %de não %f(quando precisar de decimais, é quando o display fica instável).

Embora esteja correto e para matemática não seja grande coisa, para exibição, parece estranho mostrando algo como "-0,0".

Para o método int, números negativos podem alterar o que você deseja como resultado (embora existam alguns argumentos que podem ser feitos, eles estão corretos).

A int + 0.5causa problemas reais com números -negative, a menos que você quer que ele funcione dessa maneira, mas eu imagino que a maioria das pessoas não o fazem. -0,9 provavelmente deve arredondar para -1, e não 0. Se você sabe que deseja que o negativo seja um teto em vez de um piso, você pode fazê-lo em uma linha, caso contrário, convém usar o método int com uma menor modificação (isso obviamente só funciona para recuperar números inteiros:

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

Minha solução 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 );
Akvel
fonte
0

Se você está preocupado apenas em obter um valor inteiro de um número inteiro de ponto flutuante (por exemplo, 12347.9999 ou 54321.0001), essa abordagem (emprestada e modificada acima) fará o seguinte:

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

Com muita documentação de leitura sobre como arredondar números, muitos especialistas sugerem que você escreva suas próprias rotinas de arredondamento, pois a versão em lata fornecida com o seu idioma pode não ser precisa o suficiente ou conter erros. Eu imagino, no entanto, eles estão falando muitas casas decimais e não apenas uma, duas ou três. com isso em mente, aqui está minha solução (embora não seja exatamente o necessário, pois minhas necessidades são exibir dólares - o processo não é muito 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);
}
Jarett Lloyd
fonte
para fazer a sub-rotina em conformidade com as suas especificações, simplesmente modificar o seguinte: if ($digit3 >= 5) { $digit3 = 0; $digit2++; if ($digit2 > 9) { $digit2 = 0; $digit1++; if ($digit1 > 9) { $digit1 = 0; $cost[0]++; } } } por isso é: if ($digit1 >= 5) { $digit1 = 0; $cost[0]++; } então apenasreturn 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
fonte