Li algumas respostas neste site e achei o printf
arredondamento desejável.
No entanto, quando usei na prática, um bug sutil me levou ao seguinte comportamento:
$ echo 197.5 | xargs printf '%.0f'
198
$ echo 196.5 | xargs printf '%.0f'
196
$ echo 195.5 | xargs printf '%.0f'
196
Observe que o arredondamento 196.5
se torna 196
.
Eu sei que isso pode ser algum bug sutil de ponto flutuante (mas não é um número muito grande, hein?), então alguém pode esclarecer isso?
Uma solução alternativa para isso também é muito bem-vinda (porque estou tentando colocar isso para funcionar agora).
Responder1
Como esperado, é "arredondado para par" ou "arredondamento do banqueiro".
Aresposta do site relacionadoexplique.
A questão que tal regra está tentando resolver é que (para números com uma casa decimal),
- x.1 até x.4 são arredondados para baixo.
- x.6 até x.9 são arredondados.
São 4 para baixo e 4 para cima.
Para manter o arredondamento equilibrado, precisamos arredondar x,5
- acimauma vez eabaixonas próximas.
Isto é feito pela regra: «Arredonde para o 'número par' mais próximo».
Em código:
eh LC_NUMERIC=C printf '%.0f ' "$value"
estranho echo "$value" | awk 'printf( "%s", $1)'
Opções:
No total, existem quatro maneiras possíveis de arredondar um número:
- A já explicada regra do banqueiro.
- Arredonde em direção ao +infinito. Arredondar (para números positivos)
- Arredondar para -infinito. Arredondar para baixo (para números positivos)
- Arredondar para zero. Remova os decimais (positivos ou negativos).
Acima
Se você precisar de "arredondar (em direção +infinite
)", poderá usar o awk:
value=195.5
estranho echo "$value" | awk '{ printf("%d", $1 + 0.5) }'
a.C. echo "scale=0; ($value+0.5)/1" | bc
Abaixo
Se você precisar de "arredondar para baixo (em direção -infinite
)", poderá usar:
value=195.5
estranho echo "$value" | awk '{ printf("%d", $1 - 0.5) }'
a.C. echo "scale=0; ($value-0.5)/1" | bc
Corte decimais.
Para remover os decimais (qualquer coisa após o ponto).
Também poderíamos usar diretamente o shell (funciona na maioria dos shells - é POSIX):
value="127.54" ### Works also for negative numbers.
concha echo "${value%%.*}"
estranho echo "$value"| awk '{printf ("%d",$0)}'
a.C. echo "scale=0; ($value)/1" | bc
Responder2
Não é um bug, é intencional.
Está fazendo uma espécie de rodada para o mais próximo (mais sobre isso depois).
Com exatamente .5
podemos arredondar de qualquer maneira. Na escola você provavelmente foi avisado para reunir, mas por quê? Porque você não precisa examinar mais nenhum dígito, por exemplo, 3,51, arredondado para 4; 3,5 poderia ser diferente, mas se olharmos apenas para o primeiro dígito e arredondarmos 0,5 para cima, sempre acertaremos.
Porém, se olharmos para o conjunto de decimais de 2 dígitos: 0,00 0,01, 0,02, 0,03… 0,98, 0,99, veremos que existem 100 valores, 1 é um número inteiro, 49 devem ser arredondados para cima, 49 devem ser arredondados para baixo , 1 (0,50) poderia ir para o outro lado. Se sempre arredondarmos, obteremos, em média, números 0,01 grandes demais.
Se estendermos o intervalo para 0 → 9,99, teremos 9 valores extras que são arredondados para cima. Tornando assim a nossa média um pouco maior do que o esperado. Portanto, uma tentativa de consertar isso é: 0,5 rodadas em direção ao empate. Metade das vezes arredonda para cima, metade das vezes arredonda para baixo.
Isso muda a tendência de cima para uniforme. Na maioria dos casos, isso é melhor.
Responder3
Alterar temporariamente os modos de arredondamento não é tão incomum e é possível, bin/printf
embora nãopor si sóvocê precisa alterar as fontes.
Você precisa das fontes dos coreutils, usei a versão mais recente disponível hoje que foihttp://ftp.gnu.org/gnu/coreutils/coreutils-8.24.tar.xz.
Descompacte em um diretório de sua escolha com
tar xJfv coreutils-8.24.tar.xz
Mude para o diretório de origem
cd coreutils-8.24
Carregue o arquivo src/printf.c
no editor de sua escolha e troque toda a main
função pela seguinte função, incluindo ambas as diretivas do pré-processador para incluir os arquivos de cabeçalho math.h
e fenv.h
. A função principal está no final e começa int main...
e termina no final do arquivo com o colchete de fechamento}
#include <math.h>
#include <fenv.h>
int
main (int argc, char **argv)
{
char *format;
char *rounding_env;
int args_used;
int rounding_mode;
initialize_main (&argc, &argv);
set_program_name (argv[0]);
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
atexit (close_stdout);
exit_status = EXIT_SUCCESS;
posixly_correct = (getenv ("POSIXLY_CORRECT") != NULL);
// accept rounding modes from an environment variable
if ((rounding_env = getenv ("BIN_PRINTF_ROUNDING_MODE")) != NULL)
{
rounding_mode = atoi(rounding_env);
switch (rounding_mode)
{
case 0:
if (fesetround(FE_TOWARDZERO) != 0)
{
error (0, 0, _("setting rounding mode to roundTowardZero failed"));
return EXIT_FAILURE;
}
break;
case 1:
if (fesetround(FE_TONEAREST) != 0)
{
error (0, 0, _("setting rounding mode to roundTiesToEven failed"));
return EXIT_FAILURE;
}
break;
case 2:
if (fesetround(FE_UPWARD) != 0)
{
error (0, 0, _("setting rounding mode to roundTowardPositive failed"));
return EXIT_FAILURE;
}
break;
case 3:
if (fesetround(FE_DOWNWARD) != 0)
{
error (0, 0, _("setting rounding mode to roundTowardNegative failed"));
return EXIT_FAILURE;
}
break;
default:
error (0, 0, _("setting rounding mode failed for unknown reason"));
return EXIT_FAILURE;
}
}
/* We directly parse options, rather than use parse_long_options, in
order to avoid accepting abbreviations. */
if (argc == 2)
{
if (STREQ (argv[1], "--help"))
usage (EXIT_SUCCESS);
if (STREQ (argv[1], "--version"))
{
version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version, AUTHORS,
(char *) NULL);
return EXIT_SUCCESS;
}
}
/* The above handles --help and --version.
Since there is no other invocation of getopt, handle '--' here. */
if (1 < argc && STREQ (argv[1], "--"))
{
--argc;
++argv;
}
if (argc <= 1)
{
error (0, 0, _("missing operand"));
usage (EXIT_FAILURE);
}
format = argv[1];
argc -= 2;
argv += 2;
do
{
args_used = print_formatted (format, argc, argv);
argc -= args_used;
argv += args_used;
}
while (args_used > 0 && argc > 0);
if (argc > 0)
error (0, 0,
_("warning: ignoring excess arguments, starting with %s"),
quote (argv[0]));
return exit_status;
}
Execute ./configure
da seguinte maneira
LIBS=-lm ./configure --program-suffix=-own
Ele coloca o sufixo -own
em cada subprograma (há muitos), caso você queira instalar todos eles e não tenha certeza se eles cabem no resto do sistema. Os coreutils não são nomeadosessencialutilitários sem motivo!
Mas o mais importante é estar LIBS=-lm
na frente da fila. Precisamos da biblioteca matemática e este comando diz ./configure
para adicioná-la à lista de bibliotecas necessárias.
Execute fazer
make
Se você tiver um sistema multicore/multiprocessador, tente
make -j4
onde o número (aqui "4") deve representar o número de núcleos que você deseja dispensar para esse trabalho.
Se tudo correu bem você tem o novo printf
int src/printf
. Experimente:
BIN_PRINTF_ROUNDING_MODE=1 ./src/printf '%.0f\n' 196.5
BIN_PRINTF_ROUNDING_MODE=2 ./src/printf '%.0f\n' 196.5
Ambos os comandos devem diferir na saída. Os números depois IN_PRINTF_ROUNDING_MODE
significam:
- 0Arredondando para 0
- 1Arredondando para o número mais próximo (padrão)
- 2Arredondando em direção ao infinito positivo
- 3Arredondando em direção ao infinito negativo
Você pode instalar o arquivo inteiro (não recomendado) ou apenas copiar o arquivo (renomeá-lo antes é altamente recomendado!) src/printf
em um diretório no seu PATH
e usar conforme descrito acima.
Responder4
Você pode fazer a seguinte linha curta se o que realmente deseja é arredondar para x,1 para x,4 e arredondar para x,5 para x,9.
if [[ ${a#*.} -ge "5" ]]; then a=$((${a%.*}+1)); else a=${a%.*}; fi
Ou altere "5" para o que quiser, por exemplo, "6".
PS sobre o problema com "." e/ou "," sendo usados como separadores decimais, aqui está uma solução universal fácil.
if [[ ${a##*[.,]} -ge "5" ]]; then a=$((${a%[.,]*}+1)); else a=${a%[.,]*}; fi