Je veux comparer deux nombres à virgule flottante 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
Je veux comparer deux nombres à virgule flottante 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
Réponses:
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 le dit Fered dans les commentaires, cela ne fonctionne que si les deux nombres ont des parties 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 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
1.00000000000000000000000001
c'est plus grand que 2
.
Bash ne comprend pas l'arithmétique à virgule flottante. Il traite les nombres contenant un point décimal comme des chaînes.
Utilisez plutôt awk ou bc.
#!/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 mathématiques, il est probablement préférable de s'appuyer sur python ou perl.
Vous pouvez utiliser le paquet num-utils pour des manipulations simples ...
Pour des mathématiques plus sérieuses, 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 en tête à l'entier pour donner une signification à une comparaison de gauche à droite. Ce morceau de code particulier nécessite que min et val aient en fait un 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
sortie:
min=10.35
Pour des calculs simples sur des nombres à virgule flottante (+ - * / 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 (pas bash), vous pouvez utiliser l'arithmétique intégrée de votre shell, qui prend en charge les nombres à virgule flottante.
if ((min>val)); then ((val=min)); fi
Pour des calculs en virgule flottante plus avancés, recherchez bc . Il fonctionne en fait sur les nombres de points fixes à précision arbitraire.
Pour travailler sur des tableaux de nombres, recherchez R ( exemple ).
La commande sort
a une option -g
( --general-numeric-sort
) qui peut être utilisée pour des comparaisons sur <
«moins de» 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 des nombres à virgule flottante, comme avec la notation E
$ printf '12.45E-10\n10.35\n' | sort -g | head -1
12.45E-10
Notez le E-10
, faisant le premier nombre 0.000000001245
, en effet inférieur à 10.35
.
La norme à virgule flottante, IEEE754 , définit certaines valeurs spéciales. Pour ces comparaisons, les plus intéressantes 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
Pour trouver l'utilisation maximale sort -gr
au lieu de sort -g
, inverser l'ordre de tri:
$ printf '12.45\n10.35\n' | sort -gr | head -1
12.45
Pour implémenter la <
comparaison ("moins de"), afin qu'elle puisse être utilisée dans if
etc, comparez le minimum à l'une des valeurs. Si le minimum est égal à la valeur, comparé 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
a == min(a, b)
est la même que a <= b
. Il convient de noter que cela ne vérifie pas strictement moins que cela. Si vous voulez le faire, vous devez vérifier a == min(a, b) && a != max(a, b)
, en d'autres termesa <= b and not a >= b
Utilisez simplement ksh
( ksh93
précisément) ou zsh
, qui prennent tous les deux en charge l'arithmétique en virgule flottante:
$ 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 clarifier le script publié dans la question d'ouverture peut être utilisé sans changement en dehors du commutateur shell.
Edit2: Notez que ksh93
le contenu de la variable doit être cohérent avec votre environnement local, c'est-à-dire avec un environnement local français, 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 des paramètres régionaux de l'utilisateur:
...
export LC_ALL=C
min=12.45
val=10.35
...
.
(donc pas dans la moitié du monde où se trouve le séparateur décimal ,
). zsh
n'a pas ce problème.
LC_ALL
, cela signifie également que les nombres ne seront pas affichés (ou saisis) dans le format préféré de l'utilisateur. Voir unix.stackexchange.com/questions/87745/what-does-lc-all-c-do/… pour une approche potentiellement meilleure.
.
toute façon.
min=$(echo "${min}sa ${val}d la <a p" | dc)
Cela utilise la dc
calculatrice pour s
déchirer la valeur de $min
in register a
et d
répliquer la valeur de en $val
haut de sa pile d'exécution principale. Il l
répertorie ensuite le contenu de a
sur le haut de la pile, auquel cas il ressemble à:
${min} ${val} ${val}
Le fait <
apparaître les deux premières entrées de la pile et les compare. La pile ressemble alors à:
${val}
Si l'entrée supérieure était inférieure à la seconde en haut, elle pousse le contenu de a
vers le haut, de sorte que la pile ressemble à:
${min} ${val}
Sinon, cela ne fait rien et la pile ressemble toujours à:
${val}
Ensuite, il p
imprime simplement l'entrée de la 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 vieux, bon expr
?
Exemple de syntaxe:
if expr 1.09 '>' 1.1 1>/dev/null; then
echo 'not greater'
fi
Pour les expressions vraies , le code de sortie expr est 0, avec la chaîne «1» envoyée à stdout. Inverser pour les fausses expressions.
J'ai vérifié cela avec GNU et FreeBSD 8 expr.
expr 1.09 '<' -1.1
imprimera 1
et quittera avec 0
(succès).
Pour vérifier si deux nombres (é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
Cependant, si vous voulez réellement mettre à jour une valeur minimale, vous n'avez pas besoin d'un if
. Triez les chiffres et utilisez toujours le premier (le moins):
min=12.45
val=12.55
smallest=$({ echo $min ; echo $val ; } | sort -n | head -n 1)
echo $smallest
min=$smallest
Habituellement, je fais des choses similaires avec du code python intégré:
#!/bin/sh
min=12.45
val=10.35
python - $min $val<<EOF
if ($min > $val):
print $min
else:
print $val
EOF
$ min=12.45
$ val=10.35
$ [ "$min" \< "$val" ] && echo $val || echo $min
$ 12.45
$ val=13
$ [ "$min" \< "$val" ] && echo $val || echo $min
$ 13
0.5
et0.06
). Vous feriez mieux d'utiliser un outil qui comprend déjà la notation décimale.