Comment arrondir des nombres à virgule flottante dans le shell?

13

Comment arrondir correctement les nombres à virgule flottante IEEE 754 sur la ligne de commande?

Je veux spécifier la précision du numéro de sortie - le nombre de chiffres fractionnaires.

L'arrondi 6.66à la précision 1devrait donner 6.7, par exemple. Plus dans le tableau ci-dessous:

Value   Precision  Rounded
6.66    0          7
6.66    1          6.7
6.66    2          6.66
6.66    3          6.660
6.666   3          6.666
6.6666  3          6.667

Il devrait être utilisable dans un shell interactif, mais idéalement suffisamment robuste pour l'utiliser dans des scripts shell de production.

Volker Siegel
la source
J'utiliserais awk.
isomorphismes

Réponses:

30

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 printfpeut 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 ppré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 xsur entrée standard, et pcomme 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/printfplace du shell intégré à bashou zshpour contourner les incohérences mineures dans l'implémentation des printfvariantes, et empêche un effet très sale lorsque, dans une langue allemande, LC_ALLest défini, mais pas exporté. Ensuite, le builtin utilise ,et /usr/bin/printfutilise ....


Voir aussi %gpour arrondir à un nombre spécifié de chiffres significatifs.

Volker Siegel
la source
0

La suite a fonctionné pour moi.

#!/bin/bash
function float() {
bc << EOF
num = $1;
base = num / 1;
if (((num - base) * 10) > 1 )
    base += 1;
print base;
EOF
echo ""
}

float 3.2
xs2rashid
la source