Como uso a divisão de ponto flutuante no bash?

242

Estou tentando dividir duas larguras de imagem em um script Bash, mas o bash me fornece 0o resultado:

RESULT=$(($IMG_WIDTH/$IMG2_WIDTH))

Estudei o guia Bash e sei que devo usar bc, em todos os exemplos na internet que eles usam bc. Em echoTentei colocar a mesma coisa na minha SCALE, mas não funcionou.

Aqui está o exemplo que encontrei nos tutoriais:

echo "scale=2; ${userinput}" | bc 

Como faço para que o Bash me dê um float como 0.5?

Medya Gh
fonte
9
Um comentário para todos que tentam fazer aritmética de ponto flutuante em seu script, pergunte a si mesmo: eu realmente preciso de aritmética de ponto flutuante? às vezes você pode realmente se dar bem sem. Veja, por exemplo, a última parte do BashFAQ / 022 .
gniourf_gniourf

Respostas:

242

Você não pode. festa única faz inteiros; você deve delegar para uma ferramenta como bc.

Ignacio Vazquez-Abrams
fonte
8
como delegar uma ferramenta como bc para colocar a resposta na variável RESULT?
Medya Gh 4/10/12
2
então você quer dizer VAR = $ (echo "scale = 2; $ (($ IMG_WIDTH / $ IMG2_WIDTH))" | bc)?
Medya Gh
54
@ Shevin VAR=$(echo "scale=2; $IMG_WIDTH/$IMG2_WIDTH" | bc)ou VAR=$(bc <<<"scale=2;$IMG_WIDTH/$IMG2_WIDTH")sem $ (()) (parênteses duplos); que se expandiu pela festa antes do comando executar
Nahuel FOUILLEUL
4
É verdade, mas geralmente é mais provável que o awk já esteja instalado no sistema.
CMCDragonkai
9
@NahuelFouilleul Você tem a melhor resposta aqui. Realmente deve ser sua própria resposta e aceita como resposta. Essa linha em particular foi incrivelmente útil: VAR = $ (bc <<< "scale = 2; $ IMG_WIDTH / $ IMG2_WIDTH")
David
197

você consegue fazer isso:

bc <<< 'scale=2; 100/3'
33.33

UPDATE 20130926 : você pode usar:

bc -l <<< '100/3' # saves a few hits
aayoubi
fonte
8
... e adiciona muitos dígitos. Pelo menos na minha máquina isso gera 33.33333333333333333333 enquanto o anterior fornece 33.33.
Andreas Spindler
12
@AndreasSpindler Tipo de post antigo, mas no caso de alguém querer saber, isso pode ser alterado aplicando o comando scale, por exemplo. bc -l <<< 'scale=2; 100/3'
27515 Martie
Apenas observe se você deseja obter um número inteiro para uso posterior no bash usando scale = 0. A partir da v1.06.95, bc, por algum motivo, ignora a variável de escala quando os números de entrada têm uma parte decimal. Talvez isso esteja nos documentos, mas não consegui encontrá-lo. Tente: echo $ (bc -l <<< 'scale = 0; 1 * 3.3333')
Greg Bell
1
@GregBell A página homem diz Unless specifically mentioned the scale of the result is the maximum scale of the expressions involved.E há uma nota extra para /operador:The scale of the result is the value of the variable scale.
psmith
2
Obrigado @psmith. Curiosamente, pois diz que "a escala do resultado é o valor da escala variável", mas não para a multiplicação. Meus melhores exemplos: bc <<< 'scale=1; 1*3.00001' escala é realmente 5 por algum motivo, bc <<< 'scale=1; 1/3.000001' escala é 1. Curiosamente, dividir por 1 ajusta-a: a bc <<< 'scale=1; 1*3.00001/1'escala é 1
Greg Bell
136

festança

Conforme observado por outros, bashnão suporta aritmética de ponto flutuante, embora você possa falsificá-lo com alguns truques decimais fixos, por exemplo, com dois decimais:

echo $(( 100 * 1 / 3 )) | sed 's/..$/.&/'

Resultado:

.33

Veja a resposta de Nilfred para uma abordagem semelhante, porém mais concisa.

Alternativas

Além dos mencionados bce das awkalternativas, existem também os seguintes:

clisp

clisp -x '(/ 1.0 3)'

com saída limpa:

clisp --quiet -x '(/ 1.0 3)'

ou através de stdin:

echo '(/ 1.0 3)' | clisp --quiet | tail -n1

dc

echo 2k 1 3 /p | dc

calculadora gênio cli

echo 1/3.0 | genius

ghostscript

echo 1 3 div = | gs -dNODISPLAY -dQUIET | sed -n '1s/.*>//p' 

gnuplot

echo 'pr 1/3.' | gnuplot

jq

echo 1/3 | jq -nf /dev/stdin

Ou:

jq -n 1/3

ksh

echo 'print $(( 1/3. ))' | ksh

lua

lua -e 'print(1/3)'

ou através de stdin:

echo 'print(1/3)' | lua

maxima

echo '1/3,numer;' | maxima

com saída limpa:

echo '1/3,numer;' | maxima --quiet | sed -En '2s/[^ ]+ [^ ]+ +//p'

echo 1/3 | node -p

oitava

echo 1/3 | octave

perl

echo print 1/3 | perl

python2

echo print 1/3. | python2

python3

echo 'print(1/3)' | python3

R

echo 1/3 | R --no-save

com saída limpa:

echo 1/3 | R --vanilla --quiet | sed -n '2s/.* //p'

rubi

echo print 1/3.0 | ruby

wcalc

echo 1/3 | wcalc

Com saída limpa:

echo 1/3 | wcalc | tr -d ' ' | cut -d= -f2

zsh

echo 'print $(( 1/3. ))' | zsh

unidades

units 1/3

Com saída compacta:

units --co 1/3

Outras fontes

Stéphane Chazelas respondeu a uma pergunta semelhante no Unix.SX.

Thor
fonte
9
Ótima resposta. Eu sei que foi publicado alguns anos após a pergunta, mas merece mais ser a resposta aceita.
Brian Cline
2
Se você tem zsh disponível, então vale a pena considerar para escrever seu script em zsh em vez de Bash
Andrea Corbellini
Thumb up for gnuplot :)
andywiecko
Boa lista. Especialmente echo 1/3 | node -pé curto.
Johnny Wong
39

Melhorando um pouco a resposta de marvin:

RESULT=$(awk "BEGIN {printf \"%.2f\",${IMG_WIDTH}/${IMG2_WIDTH}}")

O bc nem sempre vem como pacote instalado.

adrianlzt
fonte
4
O script awk precisa de um exitpara impedir a leitura do fluxo de entrada. Sugiro também o uso de -vbandeiras do awk para evitar a síndrome do palito de dente inclinado. Então:RESULT=$(awk -v dividend="${IMG_WIDTH}" -v divisor="${IMG2_WIDTH}" 'BEGIN {printf "%.2f", dividend/divisor; exit(0)}')
aecolley 14/03
2
Uma maneira mais awkish de fazer isso seria ler os argumentos do fluxo de entrada: RESULT=$(awk '{printf("result= %.2f\n",$1/$2)}' <<<" $IMG_WIDTH $IMG2_WIDTH ".
jmster
1
bcfaz parte do POSIX, geralmente é pré-instalado.
fuz 24/08/2015
Isso funcionou para mim usando bash git no windows 7 ... obrigado :)
A besta
31

Você pode usar bc pela -lopção (a letra L)

RESULT=$(echo "$IMG_WIDTH/$IMG2_WIDTH" | bc -l)
user1314742
fonte
4
Se eu não incluir o -lno meu sistema, o bc não fará matemática de ponto flutuante.
Starbeamrainbowlabs
28

Como alternativa ao bc, você pode usar o awk dentro do seu script.

Por exemplo:

echo "$IMG_WIDTH $IMG2_WIDTH" | awk '{printf "%.2f \n", $1/$2}'

Acima, "% .2f" informa à função printf para retornar um número de ponto flutuante com dois dígitos após a casa decimal. Eu usei eco para canalizar as variáveis ​​como campos, pois o awk opera corretamente nelas. "$ 1" e "$ 2" se referem aos primeiro e segundo campos introduzidos no awk.

E você pode armazenar o resultado como outra variável usando:

RESULT = `echo ...`
Marvin
fonte
Excelente! Obrigado. Isso é útil para ambientes incorporados nos quais bc não está presente. Você me salvou algum tempo de compilação cruzada.
Entusiasticgeek #
19

É o momento perfeito para experimentar o zsh, um superconjunto do (quase) bash, com muitos outros recursos interessantes, incluindo matemática de ponto flutuante. Aqui está como o seu exemplo seria no zsh:

% IMG_WIDTH=1080
% IMG2_WIDTH=640
% result=$((IMG_WIDTH*1.0/IMG2_WIDTH))
% echo $result
1.6875

Este post pode ajudá-lo: bash - Vale a pena mudar para zsh para uso casual?

Penghe Geng
fonte
3
Sou um grande fã do zsh e o uso há 4 anos, mas o uso interativo é uma boa ênfase aqui. Um script que requer o zsh geralmente não será muito portátil em um conjunto diversificado de máquinas, pois geralmente não é padrão, infelizmente (para ser justo, talvez esteja tudo bem; o OP não disse muito bem como será usado).
Brian Cline
17

Bem, antes do float era um tempo em que a lógica dos decimais fixos era usada:

IMG_WIDTH=100
IMG2_WIDTH=3
RESULT=$((${IMG_WIDTH}00/$IMG2_WIDTH))
echo "${RESULT:0:-2}.${RESULT: -2}"
33.33

A última linha é um bashim, se não estiver usando o bash, tente este código:

IMG_WIDTH=100
IMG2_WIDTH=3
INTEGER=$(($IMG_WIDTH/$IMG2_WIDTH))
DECIMAL=$(tail -c 3 <<< $((${IMG_WIDTH}00/$IMG2_WIDTH)))
RESULT=$INTEGER.$DECIMAL
echo $RESULT
33.33

A lógica por trás do código é: multiplique por 100 antes de dividir para obter 2 casas decimais.

Nilfred
fonte
5

Se você encontrou a variante de sua preferência, também pode envolvê-la em uma função.

Aqui estou envolvendo algum basismo em uma função div:

Um forro:

function div { local _d=${3:-2}; local _n=0000000000; _n=${_n:0:$_d}; local _r=$(($1$_n/$2)); _r=${_r:0:-$_d}.${_r: -$_d}; echo $_r;}

Ou várias linhas:

function div {
  local _d=${3:-2}
  local _n=0000000000
  _n=${_n:0:$_d}
  local _r=$(($1$_n/$2))
  _r=${_r:0:-$_d}.${_r: -$_d}
  echo $_r
}

Agora você tem a função

div <dividend> <divisor> [<precision=2>]

e use-o como

> div 1 2
.50

> div 273 123 5
2.21951

> x=$(div 22 7)
> echo $x
3.14

UPDATE Adicionei um pequeno script que fornece as operações básicas com números de ponto flutuante para o bash:

Uso:

> add 1.2 3.45
4.65
> sub 1000 .007
999.993
> mul 1.1 7.07
7.7770
> div 10 3
3.
> div 10 3.000
3.333

E aqui o script:

#!/bin/bash
__op() {
        local z=00000000000000000000000000000000
        local a1=${1%.*}
        local x1=${1//./}
        local n1=$((${#x1}-${#a1}))
        local a2=${2%.*}
        local x2=${2//./}
        local n2=$((${#x2}-${#a2}))
        local n=$n1
        if (($n1 < $n2)); then
                local n=$n2
                x1=$x1${z:0:$(($n2-$n1))}
        fi
        if (($n1 > $n2)); then
                x2=$x2${z:0:$(($n1-$n2))}
        fi
        if [ "$3" == "/" ]; then
                x1=$x1${z:0:$n}
        fi
        local r=$(($x1"$3"$x2))
        local l=$((${#r}-$n))
        if [ "$3" == "*" ]; then
                l=$(($l-$n))
        fi
        echo ${r:0:$l}.${r:$l}
}
add() { __op $1 $2 + ;}
sub() { __op $1 $2 - ;}
mul() { __op $1 $2 "*" ;}
div() { __op $1 $2 / ;}
bebbo
fonte
1
local _d=${3:-2}é mais simples
KamilCuk 5/01/19
4

Não é realmente um ponto flutuante, mas se você quiser algo que defina mais de um resultado em uma chamada de bc ...

source /dev/stdin <<<$(bc <<< '
d='$1'*3.1415926535897932384626433832795*2
print "d=",d,"\n"
a='$1'*'$1'*3.1415926535897932384626433832795
print "a=",a,"\n"
')

echo bc radius:$1 area:$a diameter:$d

calcula a área e o diâmetro de um círculo cujo raio é dado em $ 1

user3210339
fonte
4

Existem cenários nos quais você não pode usar o bc porque ele pode simplesmente não estar presente, como em algumas versões reduzidas do busybox ou sistemas incorporados. Em qualquer caso, limitar sempre as dependências externas é sempre uma boa coisa, portanto, você sempre pode adicionar zeros ao número dividido por (numerador), o mesmo que multiplicar por uma potência de 10 (você deve escolher uma potência de 10 de acordo com a precisão que você precisa), que tornará a divisão um número inteiro. Depois de ter esse número inteiro, trate-o como uma string e posicione o ponto decimal (movendo-o da direita para a esquerda) um número de vezes igual à potência de dez, multiplicado pelo numerador. Essa é uma maneira simples de obter resultados de flutuação usando apenas números inteiros.

Daniel J.
fonte
1
Até o Busybox tem o Awk. Talvez deva haver uma resposta Awk mais proeminente aqui.
Tripleee
4

Enquanto você não pode usar a divisão de ponto flutuante no Bash, pode usar a divisão de ponto fixo. Tudo o que você precisa fazer é multiplicar seus números inteiros por uma potência de 10 e depois dividir a parte inteira e usar uma operação de módulo para obter a parte fracionária. Arredondar conforme necessário.

#!/bin/bash

n=$1
d=$2

# because of rounding this should be 10^{i+1}
# where i is the number of decimal digits wanted
i=4
P=$((10**(i+1)))
Pn=$(($P / 10))
# here we 'fix' the decimal place, divide and round tward zero
t=$(($n * $P / $d + ($n < 0 ? -5 : 5)))
# then we print the number by dividing off the interger part and
# using the modulo operator (after removing the rounding digit) to get the factional part.
printf "%d.%0${i}d\n" $(($t / $P)) $(((t < 0 ? -t : t) / 10 % $Pn))
G. Allen Morris III
fonte
4

Eu sei que é velho, mas muito tentador. então, a resposta é: você não pode ... mas você pode. vamos tentar isso:

$IMG_WIDTH=1024
$IMG2_WIDTH=2048

$RATIO="$(( IMG_WIDTH / $IMG2_WIDTH )).$(( (IMG_WIDTH * 100 / IMG2_WIDTH) % 100 ))

assim você obtém 2 dígitos após o ponto, truncado (chame-o de arredondamento para baixo, haha) no bash puro (não é necessário iniciar outros processos). é claro, se você precisar apenas de um dígito após o ponto, multiplica por 10 e executa o módulo 10.

o que isso faz:

  • primeiro $ ((...)) faz divisão inteira;
  • o segundo $ ((...)) faz a divisão inteira em algo 100 vezes maior, movendo essencialmente seus 2 dígitos para a esquerda do ponto e, em seguida, (%) obtendo apenas esses 2 dígitos usando o módulo.

faixa bônus : a versão bc x 1000 levou 1,8 segundos no meu laptop, enquanto a do bash pura levou 0,016 segundos.

івась тарасик
fonte
3

Como fazer cálculos de ponto flutuante no bash:

Em vez de usar " here strings " ( <<<) com o bccomando, como um dos exemplos mais votados , aqui está o meu bcexemplo de ponto flutuante favorito , diretamente da EXAMPLESseção das bcpáginas man bcde manual (veja as páginas de manual).

Antes de começar, sabemos que uma equação para pi é: pi = 4*atan(1). a()abaixo está a bcfunção matemática para atan().

  1. É assim que se armazena o resultado de um cálculo de ponto flutuante em uma variável bash - nesse caso, em uma variável chamadapi . Observe que scale=10define o número de dígitos decimais de precisão para 10 nesse caso. Quaisquer dígitos decimais após este local são truncados .

    pi=$(echo "scale=10; 4*a(1)" | bc -l)
  2. Agora, para ter uma única linha de código que também imprime o valor dessa variável, basta adicionar o echocomando ao final como um comando de acompanhamento, conforme a seguir. Observe o truncamento com 10 casas decimais, conforme solicitado:

    pi=$(echo "scale=10; 4*a(1)" | bc -l); echo $pi
    3.1415926532
  3. Finalmente, vamos jogar alguns arredondamentos. Aqui vamos usar a printffunção para arredondar para 4 casas decimais. Note que as 3.14159...rodadas agora para 3.1416. Como estamos arredondando, não precisamos mais usar scale=10para truncar até 10 casas decimais, portanto, apenas removeremos essa parte. Aqui está a solução final:

    pi=$(printf %.4f $(echo "4*a(1)" | bc -l)); echo $pi
    3.1416

Aqui está outro ótimo aplicativo e demonstração das técnicas acima: medir e imprimir o tempo de execução.

Observe que dt_miné arredondado de 0.01666666666...para 0.017:

start=$SECONDS; sleep 1; end=$SECONDS; dt_sec=$(( end - start )); dt_min=$(printf %.3f $(echo "$dt_sec/60" | bc -l)); echo "dt_sec = $dt_sec; dt_min = $dt_min"
dt_sec = 1; dt_min = 0.017

Palavras-chave:

Gabriel Staples
fonte
1
resposta sólida com exemplos detalhados e casos de uso e rodada usando printf
downvoteit 29/04
2

Use calc. É o exemplo mais fácil que encontrei:

calc 1 + 1

 2

calc 1/10

 0.1
Camila Pereira
fonte
2

Para aqueles que tentam calcular porcentagens com a resposta aceita, mas estão perdendo precisão:

Se você executar isso:

echo "scale=2; (100/180) * 180" | bc

Você recebe 99.00apenas, o que está perdendo precisão.

Se você executar desta maneira:

echo "result = (100/180) * 180; scale=2; result / 1" | bc -l

Agora você entende 99.99.

Porque você está dimensionando apenas no momento da impressão.

Consulte aqui

Wadih M.
fonte
1

** Matemática de ponto flutuante seguro para injeção em bash / shell **

Nota: O foco desta resposta é fornecer idéias para uma solução segura para injeção para executar matemática no bash (ou em outras conchas). Obviamente, o mesmo pode ser usado, com pequenos ajustes para executar o processamento avançado de strings, etc.

A maioria das soluções apresentadas apresenta um pequeno scriptlet em tempo real, usando dados externos (variáveis, arquivos, linha de comando, variáveis ​​de ambiente). A entrada externa pode ser usada para injetar código malicioso no mecanismo, muitos deles

Abaixo está uma comparação sobre o uso de vários idiomas para realizar cálculos matemáticos básicos, onde o resultado é em ponto flutuante. Calcula A + B * 0,1 (como ponto flutuante).

Todas as tentativas de solução evitam criar scriptslet dinâmicos, que são extremamente difíceis de manter. Em vez disso, eles usam programa estático e passam parâmetros para a variável designada. Eles manipularão com segurança parâmetros com caracteres especiais - reduzindo a possibilidade de injeção de código. A exceção é 'BC', que não fornece recurso de entrada / saída

A exceção é 'bc', que não fornece nenhuma entrada / saída, todos os dados são fornecidos por programas em stdin e toda saída é enviada para stdout. Todos os cálculos estão sendo executados em uma caixa de proteção, o que não permite efeito colateral (abertura de arquivos etc.). Em teoria, injeção segura por design!

A=5.2
B=4.3

# Awk: Map variable into awk
# Exit 0 (or just exit) for success, non-zero for error.
#
awk -v A="$A" -v B="$B" 'BEGIN { print A + B * 0.1 ; exit 0}'

# Perl
perl -e '($A,$B) = @ARGV ; print $A + $B * 0.1' "$A" "$B"

# Python 2
python -c 'import sys ; a = float(sys.argv[1]) ; b = float(sys.argv[2]) ; print a+b*0.1' "$A" "$B"

# Python 3
python3 -c 'import sys ; a = float(sys.argv[1]) ; b = float(sys.argv[2]) ; print(a+b*0.1)' "$A" "$B"

# BC
bc <<< "scale=1 ; $A + $B * 0.1"
dash-o
fonte
para python3 com argumentos arbitrários: python3 -c 'import sys ; *a, = map(float, sys.argv[1:]) ; print(a[0] + a[1]*0.1 + a[2])' "$A" "$B""4200.0" ==> 4205.63
WiringHarness
0

aqui está o comando awk: -F = separador de campos == +

echo "2.1+3.1" |  awk -F "+" '{print ($1+$2)}'
koolwithk
fonte