web-dev-qa-db-fra.com

Comment utiliser la division en virgule flottante dans bash?

J'essaie de diviser deux largeurs d'image dans un script Bash, mais bash me donne 0 comme résultat:

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

J'ai étudié le guide Bash et je sais que je devrais utiliser bc, dans tous les exemples sur Internet, ils utilisent bc. Dans echo j'ai essayé de mettre la même chose dans mon SCALE mais cela n'a pas fonctionné.

Voici l'exemple que j'ai trouvé dans les tutoriels:

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

Comment puis-je faire en sorte que Bash me donne un float comme 0.5?

178
Medya Gh

Tu ne peux pas. bash seulement fait des nombres entiers; vous devez déléguer à un outil tel que bc.

192

tu peux le faire:

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

UPDATE20130926: vous pouvez utiliser:

bc -l <<< '100/3' # saves a few hits
153
aayoubi

bash

Comme d'autres l'ont noté, bash ne prend pas en charge l'arithmétique en virgule flottante, bien que vous puissiez la simuler avec des trucs décimaux fixes, par exemple avec deux décimales:

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

Sortie:

.33

Voir Nilfred's answer pour une approche similaire mais plus concise.

Des alternatives

Outre les alternatives bc et awk mentionnées, il existe également les éléments suivants:

clisp

clisp -x '(/ 1.0 3)'

avec sortie nettoyée:

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

ou par stdin:

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

dc

echo 2k 1 3 /p | dc

genius Cli Calculator

echo 1/3.0 | genius

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 par stdin:

echo 'print(1/3)' | lua

maxima

echo '1/3,numer;' | maxima

avec sortie nettoyée:

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

nœud

echo 1/3 | node -p

octave

echo 1/3 | octave

Perl

echo print 1/3 | Perl

python

echo print 1/3. | python

R

echo 1/3 | R --no-save

avec sortie nettoyée:

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

Rubis

echo print 1/3.0 | Ruby

wcalc

echo 1/3 | wcalc

Avec sortie nettoyée:

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

zsh

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

Autres sources

Stéphane Chazelasa répondu à une question similaire over sur Unix.SX.

97
Thor

Améliorant un peu la réponse de Marvin:

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

bC ne vient pas toujours comme paquet installé.

35
adrianlzt

Vous pouvez utiliser bc avec l'option -l (la lettre L)

RESULT=$(echo "$IMG_WIDTH/$IMG2_WIDTH" | bc -l)
25
user1314742

Au lieu de bc, vous pouvez utiliser awk dans votre script. 

Par exemple:

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

Dans ce qui précède, "% .2f" indique à la fonction printf de renvoyer un nombre à virgule flottante avec deux chiffres après la décimale. J'ai utilisé echo pour canaliser les variables sous forme de champs, car awk fonctionne correctement sur elles. "$ 1" et "$ 2" font référence aux premier et deuxième champs saisis dans awk. 

Et vous pouvez stocker le résultat comme une autre variable en utilisant:

RESULT = `echo ...`
24
marvin

C'est le moment idéal pour essayer zsh, un sur-ensemble (presque) bash, avec de nombreuses autres fonctionnalités de Nice, notamment le calcul en virgule flottante. Voici à quoi ressemblerait votre exemple dans zsh:

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

Ce message peut vous aider: bash - La peine de passer à zsh pour un usage occasionnel?

17
Penghe Geng

Avant float, la logique décimale fixe était utilisée:

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

La dernière ligne est un bashim, si vous n'utilisez pas bash, essayez plutôt ce code:

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

La raison d'être du code est la suivante: multipliez par 100 avant division pour obtenir 2 décimales.

12
Nilfred

Ce n'est pas vraiment un point flottant, mais si vous voulez quelque chose qui définit plus d'un résultat dans une invocation 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

calcule l'aire et le diamètre d'un cercle dont le rayon est donné en $ 1 

4
user3210339

Il existe des scénarios dans lesquels vous ne pouvez pas utiliser parce qu’il pourrait tout simplement ne pas être présent, comme dans certaines versions réduites de busybox ou de systèmes intégrés. Dans tous les cas, limiter les dépendances externes est toujours une bonne chose. Vous pouvez donc toujours ajouter des zéros au nombre divisé par (numérateur), ce qui revient à multiplier par une puissance de 10 (vous devez choisir une puissance de 10 selon la précision dont vous avez besoin), la division générera un nombre entier. Une fois que vous avez cet entier, traitez-le comme une chaîne et positionnez le point décimal (en le déplaçant de droite à gauche) un nombre de fois égal à la puissance de dix avec laquelle vous avez multiplié le numérateur. C'est un moyen simple d'obtenir des résultats flottants en utilisant uniquement des nombres entiers.

4
Daniel J.

Si vous avez trouvé la variante de votre choix, vous pouvez également l'intégrer dans une fonction.

Ici, je suis en train d'envelopper un bashisme dans une fonction div:

Bon mot:

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 multi ligne:

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
}

Maintenant vous avez la fonction

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

et l'utiliser comme

> div 1 2
.50

> div 273 123 5
2.21951

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

je sais que c'est vieux, mais trop tentant. La réponse est donc: vous ne pouvez pas ... mais vous le pouvez en quelque sorte. Essayons ça:

$IMG_WIDTH=1024
$IMG2_WIDTH=2048

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

ainsi, vous obtenez 2 chiffres après le point, tronqués (appelez-le en arrondissant au chiffre inférieur, haha) en pur bash (inutile de lancer d'autres processus). Bien sûr, si vous n'avez besoin que d'un chiffre après le point, multipliez-le par 10 et modulo 10.

qu'est-ce que cela fait:

  • premier $ ((...)) ne divise en entier;
  • second $ ((...)) effectue une division entière sur quelque chose de 100 fois plus grand, en déplaçant essentiellement vos 2 chiffres à la gauche du point, puis (%) vous obtenant uniquement ces 2 chiffres en faisant modulo.

bonus track: bc version x 1000 a pris 1,8 secondes sur mon ordinateur portable, tandis que celui de pure bash a pris 0,016 secondes.

Bien que vous ne puissiez pas utiliser la division en virgule flottante dans Bash, vous pouvez utiliser la division en point fixe. Tout ce que vous devez faire est de multiplier vos entiers par une puissance de 10, puis divisez la partie entière et utilisez une opération modulo pour obtenir la partie fractionnaire. Arrondir au besoin.

#!/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))
1

Utilisez calc. C'est l'exemple le plus simple que j'ai trouvé:

calc 1 + 1

 2

calc 1/10

 0.1
1
Camila Pereira

voici la commande awk: -F = champ separator == +

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