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
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
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.
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.
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
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 ).
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
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
.
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
Mettre en œuvre le <
("moins que") de comparaison, il peut donc être utilisé dans if
c, 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
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
...
min=$(echo "${min}sa ${val}d la <a p" | dc)
Qui utilise la calculatrice dc
sur s
Tore la valeur de $min
Dans le registre a
et d
plicate la valeur de $val
sur le dessus de sa principale pile d'exécution. Il est alors l
iste 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 p
prints 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
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.
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
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