Rundung von Gleitkommazahlen
Was bedeutet "Runden einer Gleitkommazahl"?
Das ist natürlich einfach ... Wo ist mein Mathematikbuch von der Schule ...
Nein, wir wissen bereits, dass nichts in Bezug auf Gleitkommazahlen einfach ist:
Zunächst gibt es mehrere Rundungsmodi:
Aufrunden?
Abrunden?
Auf Null runden?
Rundung auf die nächste - Bindung an gerade?
Rundung auf den nächsten Wert - von Null weg?
Wie gehe ich mit den Eckkoffern um? Wie finde ich heraus, welche Eckfälle es gibt?
OK, es sieht so aus, als würden wir besser eine Implementierung des IEEE 754-Standards verwenden und unser System sich darum kümmern lassen.
Um eine Gleitkommazahl in der Shell auf der Grundlage der Standard-Gleitkomma-Arithmetik zu runden, benötigen wir drei Schritte:
- Konvertieren Sie den Eingabetext von einem Befehlszeilenargument in eine Standard-Gleitkommazahl.
- Runden Sie die Gleitkommazahl mit der normalen IEEE 754-Implementierung.
- Formatieren Sie die Zahl als Zeichenfolge für die Ausgabe.
Es stellt sich heraus, dass der Shell-Befehl printf
all dies kann. Es kann verwendet werden, um Zahlen gemäß einer Formatspezifikation wie in beschrieben zu drucken man 3 printf
. Die Zahlen werden standardmäßig implizit gerundet, wenn dies für das Ausgabeformat erforderlich ist:
Der Befehl
Mit Genauigkeit als Befehlszeilenargumente x
auf die p
Genauigkeit der Ziffern runden :
printf "%.*f\n" "$p" "$x"
Oder in einer Shell-Pipeline mit Eingabe von x
auf Standardeingabe und p
als Argument:
echo "$x" | xargs printf "%.*f\n" "$p"
Beispiele:
$ 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
Schlechte Fallen
Vorsicht vor dem Gebietsschema! Es gibt das Trennzeichen zwischen dem Integral- und dem Bruchteil an - das .
, wie Sie vielleicht erwarten.
Aber sehen Sie selbst, was in einem deutschen Gebietsschema passiert, zum Beispiel:
$ LC_ALL=de_DE.UTF-8 printf '%.*f\n' 3 6.6666
6,667
Ja, das stimmt 6,667
- sechs Komma sechs sechs sieben. Das würde Ihr Skript sicher durcheinander bringen.
(Aber nur für die beiden Kunden in Deutschland. Mit Ausnahme der Entwicklermaschinen, die derzeit für diese Kunden debuggen.)
Robuster
Verwenden Sie:
LC_ALL=C /usr/bin/printf "%.*f\n" "$p" "$x"
oder
echo "$x" | LC_ALL=C xargs /usr/bin/printf "%.*f\n" "$p"
Dies verwendet /usr/bin/printf
anstelle der in bash
oder zsh
Implementierung kleinerer Inkonsistenzen bei der Implementierung der printf
Varianten auch die Shell und umgeht einen sehr schmutzigen Effekt, wenn in einem deutschen Gebietsschema LC_ALL
festgelegt, aber nicht exportiert wird. Dann verwendet das eingebaute ,
und /usr/bin/printf
verwendet .
...
Siehe auch %g
zum Runden auf eine bestimmte Anzahl von signifikanten Stellen.
awk
.