web-dev-qa-db-fra.com

Comment comparer deux nombres à virgule flottante dans Bash?

Je m'efforce de comparer deux nombres en virgule flottante dans un script bash. J'ai des variables, par exemple.

let num1=3.17648e-22
let num2=1.5

Maintenant, je veux juste faire une simple comparaison de ces deux nombres:

st=`echo "$num1 < $num2" | bc`
if [ $st -eq 1]; then
  echo -e "$num1 < $num2"
else
  echo -e "$num1 >= $num2"
fi

Malheureusement, le traitement correct du num1, qui peut être du "format électronique", me pose quelques problèmes. :(

Toute aide, des conseils sont les bienvenus!

97
Jonas

Plus commodément

Cela peut être fait plus facilement en utilisant le contexte numérique de Bash:

if (( $(echo "$num1 > $num2" |bc -l) )); then
  …
fi

Explication

La canalisation à travers la commande de base de la calculatrice bc renvoie 1 ou 0.

L'option -l est équivalente à --mathlib; il charge la bibliothèque mathématique standard.

Le fait de placer l'expression entière entre doubles parenthèses (( )) traduira ces valeurs en respectivement vrai ou faux.

Assurez-vous que le package bc basic calculator est installé.

102
Serge Stroobandt

bash ne traite que les mathématiques entières mais vous pouvez utiliser la commande bc comme suit:

$ num1=3.17648E-22
$ num2=1.5
$ echo $num1'>'$num2 | bc -l
0
$ echo $num2'>'$num1 | bc -l
1

Notez que le signe de l'exposant doit être en majuscule

94
alrusdi

Il vaut mieux utiliser awk pour les mathématiques non entières. Vous pouvez utiliser cette fonction utilitaire bash:

numCompare() {
   awk -v n1="$1" -v n2="$2" 'BEGIN {printf "%s " (n1<n2?"<":">=") " %s\n", n1, n2}'
}

Et appelez ça comme:

numCompare 5.65 3.14e-22
5.65 >= 3.14e-22

numCompare 5.65e-23 3.14e-22
5.65e-23 < 3.14e-22

numCompare 3.145678 3.145679
3.145678 < 3.145679
23
anubhava

Solution pure bash pour comparer des flottants sans notation exponentielle, zéros au début ou à la fin:

if [ ${FOO%.*} -eq ${BAR%.*} ] && [ ${FOO#*.} \> ${BAR#*.} ] || [ ${FOO%.*} -gt ${BAR%.*} ]; then
  echo "${FOO} > ${BAR}";
else
  echo "${FOO} <= ${BAR}";
fi

Ordre des opérateurs logiques importe . Les parties entières sont comparées en tant que nombres et les fractions sont volontairement comparées en tant que chaînes. Les variables sont scindées en parties entières et fractionnaires à l'aide de cette méthode .

Ne comparera pas les flottants aux entiers (sans point).

20
user

vous pouvez utiliser awk associé à bash si condition, awk affichera 1 ou 0 et ceux-ci seront interprétés par la clause if avec true ou false .

if (( $(awk 'BEGIN {print ("'$d1'" >= "'$d2'")}') )); then
    echo "yes"
else 
    echo "no"
fi
10
ungalcrys

méfiez-vous lorsque vous comparez des numéros de version de package, par exemple en vérifiant si grep 2.20 est supérieur à la version 2.6:

$ awk 'BEGIN { print (2.20 >= 2.6) ? "YES" : "NO" }'
NO

$ awk 'BEGIN { print (2.2 >= 2.6) ? "YES" : "NO" }'
NO

$ awk 'BEGIN { print (2.60 == 2.6) ? "YES" : "NO" }'
YES

J'ai résolu ce problème avec une telle fonction Shell/awk:

# get version of GNU tool
toolversion() {
    local prog="$1" operator="$2" value="$3" version

    version=$($prog --version | awk '{print $NF; exit}')

    awk -vv1="$version" -vv2="$value" 'BEGIN {
        split(v1, a, /\./); split(v2, b, /\./);
        if (a[1] == b[1]) {
            exit (a[2] '$operator' b[2]) ? 0 : 1
        }
        else {
            exit (a[1] '$operator' b[1]) ? 0 : 1
        }
    }'
}

if toolversion grep '>=' 2.6; then
   # do something awesome
fi
5
Elan Ruusamäe

J'ai utilisé les réponses d'ici et les ai mises dans une fonction, vous pouvez l'utiliser comme ceci:

is_first_floating_number_bigger 1.5 1.2
result="${__FUNCTION_RETURN}"

Une fois appelé, echo $result sera 1 dans ce cas, sinon 0.

La fonction:

is_first_floating_number_bigger () {
    number1="$1"
    number2="$2"

    [ ${number1%.*} -eq ${number2%.*} ] && [ ${number1#*.} \> ${number2#*.} ] || [ ${number1%.*} -gt ${number2%.*} ];
    result=$?
    if [ "$result" -eq 0 ]; then result=1; else result=0; fi

    __FUNCTION_RETURN="${result}"
}

Ou une version avec sortie de débogage:

is_first_floating_number_bigger () {
    number1="$1"
    number2="$2"

    echo "... is_first_floating_number_bigger: comparing ${number1} with ${number2} (to check if the first one is bigger)"

    [ ${number1%.*} -eq ${number2%.*} ] && [ ${number1#*.} \> ${number2#*.} ] || [ ${number1%.*} -gt ${number2%.*} ];
    result=$?
    if [ "$result" -eq 0 ]; then result=1; else result=0; fi

    echo "... is_first_floating_number_bigger: result is: ${result}"

    if [ "$result" -eq 0 ]; then
        echo "... is_first_floating_number_bigger: ${number1} is not bigger than ${number2}"
    else
        echo "... is_first_floating_number_bigger: ${number1} is bigger than ${number2}"
    fi

    __FUNCTION_RETURN="${result}"
}

Enregistrez simplement la fonction dans un fichier .sh séparé et incluez-la comme ceci:

. /path/to/the/new-file.sh
3
Thomas Kekeisen

Ce script peut aider lorsque je vérifie si la version grails installée est supérieure au minimum requis. J'espère que ça aide. 

#!/bin/bash                                                                                         

min=1.4                                                                                             
current=`echo $(grails --version | head -n 2 | awk '{print $NF}' | cut -c 1-3)`                         

if [ 1 -eq `echo "${current} < ${min}" | bc` ]                                                          
then                                                                                                
    echo "yo, you have older version of grails."                                                   
else                                                                                                                                                                                                                       
    echo "Hurray, you have the latest version" 
fi
2
prayagupd

Bien sûr, si vous n’avez pas besoin d’arithmétique à virgule flottante, arithmétique sur, par exemple. Les valeurs en dollars où il y a toujours exactement deux chiffres décimaux, vous pouvez simplement supprimer le point (en multipliant effectivement par 100) et comparer les entiers résultants.

if [[ ${num1/.} < ${num2/.} ]]; then
    ...

Cela nécessite évidemment que vous soyez sûr que les deux valeurs ont le même nombre de décimales.

1
tripleee

veuillez vérifier le code édité ci-dessous: -

#!/bin/bash

export num1=(3.17648*e-22)
export num2=1.5

st=$((`echo "$num1 < $num2"| bc`))
if [ $st -eq 1 ]
  then
    echo -e "$num1 < $num2"
  else
    echo -e "$num1 >= $num2"
fi

ça marche bien.

1
gopika

Utilisez korn Shell, vous devrez peut-être comparer séparément la partie décimale

#!/bin/ksh
X=0.2
Y=0.2
echo $X
echo $Y

if [[ $X -lt $Y ]]
then
     echo "X is less than Y"
Elif [[ $X -gt $Y ]]
then
     echo "X is greater than Y"
Elif [[ $X -eq $Y ]]
then
     echo "X is equal to Y"
fi
1
Alan Joseph

En utilisant bashj ( https://sourceforge.net/projects/bashj/ ), un mutant bash supportant Java, il vous suffit d’écrire (et cela IS facile à lire):

#!/usr/bin/bashj

#!Java
static int doubleCompare(double a,double b) {return((a>b) ? 1 : (a<b) ? -1 : 0);}

#!bashj
num1=3.17648e-22
num2=1.5
comp=j.doubleCompare($num1,$num2)
if [ $comp == 0 ] ; then echo "Equal" ; fi
if [ $comp == 1 ] ; then echo "$num1 > $num2" ; fi
if [ $comp == -1 ] ; then echo "$num2 > $num1" ; fi

Bien sûr, l'hybridation bashj bash/Java offre beaucoup plus ...

1
Fil

Une solution prenant en charge la notation scientifique avec les exposants majuscules et minuscules (par exemple, 12.00e4):

if (( $(bc -l <<< "${value1/e/E} < ${value2/e/E}") ))
then
    echo "$value1 is less than $value2"
fi 
0
Danila Piatov

Que dis-tu de ça? = D

VAL_TO_CHECK="1.00001"
if [ $(awk '{printf($1 >= $2) ? 1 : 0}' <<<" $VAL_TO_CHECK 1 ") -eq 1 ] ; then
    echo "$VAL_TO_CHECK >= 1"
else
    echo "$VAL_TO_CHECK < 1"
fi
0
Eduardo Lucio
num1=0.555
num2=2.555


if [ `echo "$num1>$num2"|bc` -eq 1 ]; then
       echo "$num1 is greater then $num2"
else
       echo "$num2 is greater then $num1"
fi
0
rmil

Je publiais ceci comme une réponse à https://stackoverflow.com/a/56415379/1745001 quand il a été fermé comme un dup de cette question, donc voici ce qui est appliqué ici:

Pour des raisons de simplicité et de clarté, utilisez simplement awk pour les calculs car il s’agit d’un outil UNIX standard et donc tout aussi probable d’être présent que bc et beaucoup plus facile à utiliser syntaxiquement.

Pour cette question:

$ cat tst.sh
#!/bin/bash

num1=3.17648e-22
num2=1.5

awk -v num1="$num1" -v num2="$num2" '
BEGIN {
    print "num1", (num1 < num2 ? "<" : ">="), "num2"
}
'

$ ./tst.sh
num1 < num2

et pour cette autre question qui était fermée comme un dup de celle-ci:

$ cat tst.sh
#!/bin/bash

read -p "Operator: " operator
read -p "First number: " ch1
read -p "Second number: " ch2

awk -v ch1="$ch1" -v ch2="$ch2" -v op="$operator" '
BEGIN {
    if ( ( op == "/" ) && ( ch2 == 0 ) ) {
        print "Nope..."
    }
    else {
        print ch1 '"$operator"' ch2
    }
}
'

$ ./tst.sh
Operator: /
First number: 4.5
Second number: 2
2.25

$ ./tst.sh
Operator: /
First number: 4.5
Second number: 0
Nope...
0
Ed Morton

awk et des outils comme celui-ci (je vous regarde sed...) devraient être relégués à la poubelle d'anciens projets, avec un code que tout le monde a trop peur de toucher car il a été écrit dans un langage illimité.

Ou vous êtes le projet relativement rare qui doit prioriser l'optimisation de l'utilisation du processeur par rapport à l'optimisation de la maintenance du code ... dans ce cas, poursuivez.

Sinon, pourquoi ne pas utiliser simplement quelque chose de lisible et explicite, tel que python? Vos collègues codeurs et futurs collaborateurs vous remercieront. Vous pouvez utiliser python inline avec bash comme tous les autres.

num1=3.17648E-22
num2=1.5
if python -c "import sys; sys.exit(0 if float($num1) < float($num2) else 1)"; then
    echo "yes, $num1 < $num2"
else
    echo "no, $num1 >= $num2"
fi
0
CivFan