Como converter um número de ponto flutuante para inteiro?

47

Essa é a maneira correta de fazer a conversão de float para número inteiro no bash? Existe algum outro método?

flotToint() {
    printf "%.0f\n" "$@"
}
Rahul Patil
fonte
5
O que é certo"?
Joseph R.
Eu só quero ter certeza .. isso está certo ou existe melhor do que isso?
Rahul Patil
2
%.0farredondará para cima ou para baixo. É isso que você quer? Você pode usar printf "%d\n" "$@" 2>/dev/nullpara cortar a fração.
#

Respostas:

70

bater

Em bash, isso é provavelmente tão bom quanto ele ganha. Isso usa um shell embutido. Se você precisar do resultado em uma variável, poderá usar a substituição de comandos ou o bashespecífico (embora agora também seja suportado por zsh):

printf -v int %.0f "$float"

Você poderia fazer:

float=1.23
int=${float%.*}

Mas isso removeria a parte fracionária em vez de fornecer o número inteiro mais próximo e não funcionaria para valores $floatiguais 1.2e9ou .12por exemplo.

Observe também as possíveis limitações devido à representação interna dos carros alegóricos:

$ printf '%.0f\n' 1e50
100000000000000007629769841091887003294964970946560

Você recebe um número inteiro, mas é provável que não possa usá-lo em nenhum lugar.

Além disso, como observado pelo @BinaryZebra, em várias printfimplementações (bash, ksh93, yash, não GNU, zsh, dash), ele é afetado pelo código do idioma (o separador decimal que pode ser .ou ,).

Portanto, se seus carros alegóricos são sempre expressos com o período como separador decimal e você deseja que ele seja tratado como tal, printfindependentemente do código do idioma do usuário que está invocando seu script, será necessário corrigir o código do idioma para C:

LC_ALL=C printf '%.0f' "$float"

Com yash, você também pode fazer:

printf '%.0f' "$(($float))"

(ver abaixo).

POSIX

printf "%.0f\n" 1.1

não é POSIX, pois %fnão é necessário ter suporte do POSIX.

POSIXly, você pode fazer:

f2i() {
  awk 'BEGIN{for (i=1; i<ARGC;i++)
   printf "%.0f\n", ARGV[i]}' "$@"
}

Esse não é afetado pelo código do idioma (a vírgula não pode ser um separador decimal, awkpois já existe um caractere especial na sintaxe (o print 1,2mesmo que print 1, 2passar dois argumentos para print)

zsh

Em zsh(que suporta aritmética de ponto flutuante (o separador decimal é sempre o período)), você tem a rint()função matemática para fornecer o número inteiro mais próximo como ponto flutuante (como em C) e int()para fornecer um número inteiro a partir de um ponto flutuante (como em awk). Então você pode fazer:

$ zmodload zsh/mathfunc
$ i=$((int(rint(1.234e2))))
$ echo $i
123

Ou:

$ integer i=$((rint(5.678e2)))
$ echo $i
568

No entanto, observe que, enquanto doubles pode representar números muito grandes, números inteiros são muito mais limitados.

$ printf '%.0f\n' 1e123
999999999999999977709969731404129670057984297594921577392083322662491290889839886077866558841507631684757522070951350501376
$ echo $((int(1e123)))
-9223372036854775808

ksh93

O ksh93 foi o primeiro shell tipo Bourne a suportar aritmética de ponto flutuante. O ksh93 otimiza a substituição de comandos, não usando um pipe ou bifurcação quando os comandos são apenas comandos internos. assim

i=$(printf '%.0f' "$f")

não bifurca. Ou melhor ainda:

i=${ printf '%.0f' "$f"; }

que não bifurca, mas também não dá todo o trabalho de criar um ambiente de subcamação falso.

Você também pode fazer:

i=$((rint(f)))

Mas cuidado com:

$ echo "$((rint(1e18)))"
1000000000000000000
$ echo "$((rint(1e19)))"
1e+19

Você também pode fazer:

integer i=$((rint(f)))

Mas como para zsh:

$ integer i=1e18
$ echo "$i"
1000000000000000000
$ integer i=1e19
$ echo "$i"
-9223372036854775808

Cuidado com o fato de que a ksh93aritmética de ponto flutuante honra a configuração do separador decimal no código do idioma (mesmo que ,seja um operador matemático ( $((1,2))seria 6/5 em um código de idioma francês / alemão ... e o mesmo $((1, 2))que é 2 em um código de idioma em inglês) .

yash

O yash também suporta aritmética de ponto flutuante, mas não possui funções matemáticas como ksh93/ zsh's rint(). Você pode converter um número em número inteiro usando o binário ou o operador, por exemplo (também funciona em, zshmas não em ksh93). Observe, no entanto, que ele trunca a parte decimal, não fornece o número inteiro mais próximo:

$ echo "$((0.237e2 | 0))"
23
$ echo "$((1e19))"
-9223372036854775808

yash honra o separador decimal da localidade na saída, mas não para as constantes literais de ponto flutuante em suas expressões aritméticas, o que pode causar surpresas:

$ LC_ALL=fr_FR.UTF-8 ./yash -c 'a=$((1e-2)); echo $(($a + 1))'
./yash: arithmetic: `,' is not a valid number or operator

É bom, de certa forma, que você possa usar constantes de ponto flutuante em seus scripts que usam o período e não tenha que se preocupar que ele pare de funcionar em outros locais, mas ainda assim seja capaz de lidar com os números expressos pelo usuário desde como você se lembra de fazer:

var=$((10.3)) # and not var=10.3
... "$((a + 0.1))" # and not "$(($a + 0.1))".

printf '%.0f\n' "$((10.3))" # and not printf '%.0f\n' 10.3
Stéphane Chazelas
fonte
int=${float%.*}funcionou muito bem no meu script bash (versão 3.2.57 (1) -release)) em um Mac (versão 10.13.4 (17E199)).
TelamonAegisthus
Sua primeira formulação falha no GNU bash 4.4.19, por exemplo: `` `$ echo $ maximum 32.800 $ printf -v int% .0f" $ maximum "bash: printf: 32.800: número inválido` `
Luís de Sousa
@ LuísdeSousa, seu código do idioma provavelmente tem ,o valor decimal. Veja a seção sobreLC_ALL=C
Stéphane Chazelas
Observe que esta resposta responde a uma pergunta diferente: Como remover dígitos após o ponto, não o que foi perguntado: qual é a maneira correta de fazer flutuar para inteiro .
Isaac
Esse método deve ser aplicado a flutuadores de qualquer número de bits? Deve ser o mesmo para uma flutuação de mantissa de 23 bits ou 128 bits?
Isaac
21

bc - Uma linguagem de calculadora de precisão arbitrária

int (float) deve se parecer com:

$ echo "$float/1" | bc 
1234

Para arredondar melhor use isso:

$ echo "($float+0.5)/1" | bc 

Exemplo:

$ float=1.49
$ echo "($float+0.5)/1" | bc 
1
$ float=1.50
$ echo "($float+0.5)/1" | bc 
2
mundo inocente
fonte
4
Mas float=-2; echo "($float+0.5)/1" | bc-1.
Stéphane Chazelas 12/08
2
Isso é chamado de round para + inf. Essa é uma das quatro maneiras possíveis de arredondar .
5

A resposta anterior enviada estava quase correta: "Você poderia fazer:

float=1.23
int=${float%.*}

Mas isso removeria a parte fracionária em vez de fornecer o número inteiro mais próximo e não funcionaria para valores de $ float como 1,2e9 ou 0,12, por exemplo ... "

Apenas use ${float%%.*}.

echo ${float%%.*}
1
Jay Mayers
fonte
3

Um hacky muito simples é

function float_to_int() { 
  echo $1 | cut -d. -f1    # or use -d, if decimals separator is ,
}

Saída de amostra

$ float_to_int 32.333
32
$ float_to_int 32
32
GMaster
fonte
Seria possível usar sed, mas a cutsolução é mais simples. Eu prefiro sedou cutcomo eles são IMHO mais acessível em alvos embutidos do que awk(que ainda é bastante comum através busyboxde bc).
pevik 19/09
0

Relacionado:

  1. como fazer float para número inteiro no awk ,
  2. Como arredondar números de ponto flutuante no shell?

Complementando a resposta de Stéphane Chazelas awk :

float_to_integer_rounding_nearst() {
    awk 'BEGIN{for (i=1; i<ARGC;i++) printf "%.0f", ARGV[i]}' "$@";
}

float_to_integer_rounding_floor() {
    awk 'BEGIN{for (i=1; i<ARGC;i++) printf "%d", int( ARGV[i] )}' "$@";
}
do utilizador
fonte
nos meus awks, o último se volta para zero, e não para o infinito (como "andar" pode significar). Além disso, as primeiras rodadas são reduzidas à metade para números pares. Não tenho certeza se isso é diferente do que o Bash printffaz, ou se há algum outro motivo para preferir o awk do que o incorporado printf.
ilkkachu 13/10