Arrondir les nombres à virgule flottante
Que signifie «arrondir un nombre à virgule flottante»?
C'est facile, évidemment ... Où est mon livre de mathématiques de l'école ...
Non, nous savons déjà que rien en rapport avec les nombres à virgule flottante n'est facile:
Pour commencer, il existe plusieurs modes d'arrondi:
Arrondir vers le haut?
Arrondir vers le bas?
Arrondi à zéro?
Arrondi au plus proche - égal à égal?
Arrondi au plus proche - liens loin de zéro?
Comment gérer les valises d'angle? Comment savoir quels sont les cas d'angle?
OK, il semble que nous devrions mieux utiliser une implémentation de la norme IEEE 754 et laisser notre système s'en occuper.
Pour arrondir un nombre à virgule flottante dans le shell, basé sur l'arithmétique à virgule flottante standard, nous avons besoin de trois étapes:
- Convertissez le texte d'entrée d'un argument de ligne de commande en un nombre à virgule flottante standard.
- Arrondissez le nombre à virgule flottante à l'aide de l'implémentation IEEE 754 normale.
- Formatez le nombre sous forme de chaîne pour la sortie.
Il s'avère que la commande shell printf
peut faire tout cela. Il peut être utilisé pour imprimer des nombres selon une spécification de format comme décrit dans man 3 printf
. Les nombres sont arrondis implicitement de la manière standard si cela est requis pour le format de sortie:
La commande
Arrondir x
à la p
précision des chiffres avec entrée comme arguments de ligne de commande:
printf "%.*f\n" "$p" "$x"
Ou dans un pipeline shell, avec entrée de x
sur entrée standard, et p
comme argument:
echo "$x" | xargs printf "%.*f\n" "$p"
Exemples:
$ printf '%.*f\n' 0 6.66
7
$ printf '%.*f\n' 1 6.66
6.7
$ printf '%.*f\n' 2 6.66
6.66
$ printf '%.*f\n' 3 6.66
6.660
$ printf '%.*f\n' 3 6.666
6.666
$ printf '%.*f\n' 3 6.6666
6.667
Mauvais pièges
Méfiez-vous des paramètres régionaux! Il spécifie le séparateur entre la partie intégrale et la partie fractionnaire - le .
, comme vous pouvez vous y attendre.
Mais voyez-vous ce qui se passe dans une langue allemande, par exemple:
$ LC_ALL=de_DE.UTF-8 printf '%.*f\n' 3 6.6666
6,667
Oui, c'est vrai 6,667
- six virgules six six sept. Cela gâcherait votre script à coup sûr.
(Mais uniquement pour les deux clients en Allemagne. À l'exception des machines du développeur en cours de débogage pour ces clients.)
Plus robuste
Pour le rendre plus robuste, utilisez:
LC_ALL=C /usr/bin/printf "%.*f\n" "$p" "$x"
ou
echo "$x" | LC_ALL=C xargs /usr/bin/printf "%.*f\n" "$p"
Cela utilise également à la /usr/bin/printf
place du shell intégré à bash
ou zsh
pour contourner les incohérences mineures dans l'implémentation des printf
variantes, et empêche un effet très sale lorsque, dans une langue allemande, LC_ALL
est défini, mais pas exporté. Ensuite, le builtin utilise ,
et /usr/bin/printf
utilise .
...
Voir aussi %g
pour arrondir à un nombre spécifié de chiffres significatifs.
awk
.