Respostas:
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 trunca0
. Para arredondar para um determinado número de dígitos,sprintf()
ouprintf()
geralmente é a rota mais fácil.
printf("%.3f", 3.1415926535); # prints 3.142
O
POSIX
módulo (parte do padrão de distribuição de Perl) implementaceil()
,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, oMath::Trig
módulo (parte da distribuição Perl padrão) implementa as funções trigonométricas. Internamente, ele usa oMath::Complex
mó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.
printf
se você quiser o resultado em uma variável, uso sprintf
... espero que isso poupa-lhe algum tempo de depuração :-P
int()
em PDLs?
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ê $float
seja 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.
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;
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
Veja perldoc / perlfaq :
Lembre-se de que
int()
apenas trunca para 0. Para arredondar para um determinado número de dígitos,sprintf()
ouprintf()
geralmente é a rota mais fácil.printf("%.3f",3.1415926535); # prints 3.142
O
POSIX
módulo (parte do padrão de distribuição de Perl) implementaceil()
,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::Trig
módulo (parte da distribuição Perl padrão)> implementa as funções trigonométricas.Internamente, ele usa o
Math::Complex
mó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.
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.
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);
}
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
.
Os números negativos podem adicionar algumas peculiaridades que as pessoas precisam estar cientes.
printf
abordagens 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 printf
abordagem de estilo e souber que não deseja decimal, use %d
e 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.5
causa 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;
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);
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);
}
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]);
cat table |
perl -ne '/\d+\s+(\d+)\s+(\S+)/ && print "".**int**(log($1)/log(2))."\t$2\n";'