web-dev-qa-db-fra.com

Comment comparer le numéro de point flottant dans un script shell

Je veux comparer deux numéros de points flottants dans un script shell. Le code suivant ne fonctionne pas:

#!/bin/bash   
min=12.45
val=10.35    
if (( $val < $min )) ; then    
  min=$val
fi
echo $min 
22
RIchard Williams

Vous pouvez vérifier séparément les parties entières et fractionnaires:

#!/bin/bash
min=12.45
val=12.35    
if (( ${val%%.*} < ${min%%.*} || ( ${val%%.*} == ${min%%.*} && ${val##*.} < ${min##*.} ) )) ; then    
    min=$val
fi
echo $min

Comme indique dans les commentaires, cela ne fonctionne que si les deux chiffres ont des pièces fractionnaires et que les deux parties fractionnaires ont le même nombre de chiffres. Voici une version qui fonctionne pour entier ou fractionnaire et tout opérateur de bash:

#!/bin/bash
shopt -s extglob
fcomp() {
    local oldIFS="$IFS" op=$2 x y digitx digity
    IFS='.' x=( ${1##+([0]|[-]|[+])}) y=( ${3##+([0]|[-]|[+])}) IFS="$oldIFS"
    while [[ "${x[1]}${y[1]}" =~ [^0] ]]; do
        digitx=${x[1]:0:1} digity=${y[1]:0:1}
        (( x[0] = x[0] * 10 + ${digitx:-0} , y[0] = y[0] * 10 + ${digity:-0} ))
        x[1]=${x[1]:1} y[1]=${y[1]:1} 
    done
    [[ ${1:0:1} == '-' ]] && (( x[0] *= -1 ))
    [[ ${3:0:1} == '-' ]] && (( y[0] *= -1 ))
    (( ${x:-0} $op ${y:-0} ))
}

for op in '==' '!=' '>' '<' '<=' '>='; do
    fcomp $1 $op $2 && echo "$1 $op $2"
done
6
ata

Bash ne comprend pas l'arithmétique de point flottant. Il traite des nombres contenant un point décimal comme des chaînes.

Utilisez AWK ou BC à la place.

#!/bin/bash

min=12.45
val=10.35

if [ 1 -eq "$(echo "${val} < ${min}" | bc)" ]
then  
    min=${val}
fi

echo "$min"

Si vous avez l'intention de faire beaucoup d'opérations de mathématiques, il est probablement préférable de compter sur python ou Perl.

35
Daniel Näslund

Vous pouvez utiliser un package num-utils pour des manipulations simples ...

Pour des mathématiques plus graves, voir ce lien ... Il décrit plusieurs options, par exemple.

  • r/rscript (Calcul statistique GNU R et système graphique)
  • octave (principalement compatible MATLAB)
  • bC (le GNU Calculateur de précision arbitraire de la Colombie-Britannique)

Un exemple de numprocess

echo "123.456" | numprocess /+33.267,%2.33777/
# 67.0395291239087  

A programs for dealing with numbers from the command line

The 'num-utils' are a set of programs for dealing with numbers from the
Unix command line. Much like the other Unix command line utilities like
grep, awk, sort, cut, etc. these utilities work on data from both
standard in and data from files.

Includes these programs:
 * numaverage: A program for calculating the average of numbers.
 * numbound: Finds the boundary numbers (min and max) of input.
 * numinterval: Shows the numeric intervals between each number in a sequence.
 * numnormalize: Normalizes a set of numbers between 0 and 1 by default.
 * numgrep: Like normal grep, but for sets of numbers.
 * numprocess: Do mathematical operations on numbers.
 * numsum: Add up all the numbers.
 * numrandom: Generate a random number from a given expression.
 * numrange: Generate a set of numbers in a range expression.
 * numround: Round each number according to its value.

Voici un bash hack ... Il ajoute des 0 premiers 0 à l'entier pour faire une chaîne de comparaison de gauche à droite significative. Ce morceau de code particulier nécessite que les deux min et val ont effectivement une point décimal et au moins un chiffre décimal.

min=12.45
val=10.35

MIN=0; VAL=1 # named array indexes, for clarity
IFS=.; tmp=($min $val); unset IFS 
tmp=($(printf -- "%09d.%s\n" ${tmp[@]}))
[[ ${tmp[VAL]} < ${tmp[MIN]} ]] && min=$val
echo min=$min

sortir:

min=10.35
12
Peter.O

Pour des calculs simples sur les numéros de points flottants (+ - */et des comparaisons), vous pouvez utiliser AWK.

min=$(echo 12.45 10.35 | awk '{if ($1 < $2) print $1; else print $2}')

Ou, si vous avez KSH93 ou ZSH (non bash), vous pouvez utiliser l'arithmétique intégrée de votre coquille, qui prend en charge les numéros de points flottants.

if ((min>val)); then ((val=min)); fi

Pour des calculs de points flottants plus avancés, recherchez-la BC . Cela fonctionne réellement sur les numéros de fixation de précision arbitraire.

Travailler sur des tables de nombres, recherchez-la [~ # ~] r [~ # ~ ~] ( exemple ).

Utilisez le tri numérique

La commande sort a une option -g (--general-numeric-sort) qui peut être utilisé pour des comparaisons sur <, "moins que" ou >, "plus grand que", en trouvant le minimum ou le maximum.

Ces exemples trouvent le minimum:

$ printf '12.45\n10.35\n' | sort -g | head -1
10.35

Prend en charge la notation e

Il fonctionne avec une notation assez générale de nombres de points flottants, comme avec la notation e

$ printf '12.45E-10\n10.35\n' | sort -g | head -1
12.45E-10

Noter la E-10, faire le premier numéro 0.000000001245, en effet moins que 10.35.

Peut comparer à l'infini

La norme de point flottant, IEEE754 , définit certaines valeurs spéciales. Pour ces comparaisons, les intéressants sont INF pour l'infini. Il y a aussi l'infini négatif; Les deux sont des valeurs bien définies dans la norme.

$ printf 'INF\n10.35\n' | sort -g | head -1
10.35
$ printf '-INF\n10.35\n' | sort -g | head -1
-INF

Trouver l'utilisation maximale sort -gr à la place de sort -g, inversant l'ordre de tri:

$ printf '12.45\n10.35\n' | sort -gr | head -1
12.45

Opération de comparaison

Mettre en œuvre le < ("moins que") de comparaison, il peut donc être utilisé dans ifc, comparer le minimum à l'une des valeurs. Si le minimum est égal à la valeur par rapport au texte , il est inférieur à l'autre valeur:

$ a=12.45; b=10.35                                    
$ [ "$a" = "$(printf "$a\n$b\n" | sort -g | head -1)" ]
$ echo $?
1
$ a=12.45; b=100.35                                    
$ [ "$a" = "$(printf "$a\n$b\n" | sort -g | head -1)" ]
$ echo $?                                              
0
6
Volker Siegel

Il suffit d'utiliser ksh (ksh93 précisément) ou zsh, qui soutiennent tous deux de manière native les arithmétiques de points flottants:

$ cat test.ksh
#!/bin/ksh 
min=12.45
val=10.35    
if (( $val < $min )) ; then    
  min=$val
fi
echo "$min"
$ ./test.ksh
10.35

Edit: Désolé, j'ai raté ksh93 a déjà été suggéré. Garder ma réponse juste pour préciser le script publié dans la question d'ouverture peut être utilisé sans changement en dehors du commutateur Shell.

EDIT2: Notez que ksh93 exige que le contenu variable soit cohérent avec votre lieu local, c'est-à-dire avec une locale française, une virgule au lieu d'un point doit être utilisée:

...
min=12,45
val=10,35
...

Une solution plus robuste consiste à définir les paramètres régionaux au début du script pour vous assurer qu'il fonctionnera indépendamment de la locale de l'utilisateur:

...
export LC_ALL=C
min=12.45
val=10.35
...
3
jlliagre
min=$(echo "${min}sa ${val}d la <a p" | dc)

Qui utilise la calculatrice dc sur sTore la valeur de $min Dans le registre a et dplicate la valeur de $val sur le dessus de sa principale pile d'exécution. Il est alors liste le contenu de a sur le dessus de la pile, auquel on ressemble:

${min} ${val} ${val}

Les < apparaît les deux meilleures entrées de la pile et les compare. Donc, la pile ressemble alors à:

${val}

Si l'entrée supérieure était inférieure au second à haut, il pousse le contenu de a sur le dessus, de sorte que la pile ressemble à:

${min} ${val}

Sinon ce n'est rien et la pile ressemble toujours à:

${val} 

Ensuite, il suffit de pprints l'entrée de pile supérieure.

Donc, pour votre problème:

min=12.45
val=12.35
echo "${min}sa ${val}d la <a p" | dc

###OUTPUT

12.35

Mais:

min=12.45
val=12.55
echo "${min}sa ${val}d la <a p" | dc

###OUTPUT

12.45
1
mikeserv

Pourquoi ne pas utiliser de vieux, bon expr?

Exemple de syntaxe:

if expr 1.09 '>' 1.1 1>/dev/null; then
    echo 'not greater'
fi

Pour True expressions, code de sortie EXPR est 0, avec chaîne '1' envoyée à stdout. Inverser pour faux expressions.

J'ai vérifié cela avec GNU et FreeBSD 8 Expr.

0
sam_pan_mariusz

Habituellement, je fais des choses similaires avec intégrée python code:

#!/bin/sh

min=12.45
val=10.35

python - $min $val<<EOF
if ($min > $val):
        print $min
else: 
        print $val
EOF
0
dganesh2002

Pour vérifier si deux numéros (éventuellement fractionnaires) sont en ordre, sort est (raisonnablement) portable:

min=12.45
val=12.55
if { echo $min ; echo $val ; } | sort -n -c 2>/dev/null
then
  echo min is smallest
else
  echo val is smallest
fi

Toutefois, si vous souhaitez conserver une valeur minimale mise à jour, vous n'avez pas besoin de if. Trier les chiffres et toujours utiliser le premier (le moins):

min=12.45
val=12.55
smallest=$({ echo $min ; echo $val ; } | sort -n | head -n 1)
echo $smallest
min=$smallest
0
David Jones