Wie runde ich Gleitkommazahlen in der Shell?

13

Wie runde ich IEEE 754-Gleitkommazahlen in der Befehlszeile korrekt?

Ich möchte die Genauigkeit der Ausgabenummer angeben - die Anzahl der Bruchstellen.

Eine 6.66präzise Rundung 1sollte 6.7zum Beispiel ergeben. Mehr in der folgenden Tabelle:

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

Es sollte in einer interaktiven Shell verwendbar sein, aber idealerweise robust genug, um es in Produktions-Shell-Skripten zu verwenden.

Volker Siegel
quelle
Ich würde verwenden awk.
Isomorphismen

Antworten:

30

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 printfall 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 xauf die pGenauigkeit der Ziffern runden :

printf "%.*f\n" "$p" "$x"

Oder in einer Shell-Pipeline mit Eingabe von xauf Standardeingabe und pals 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/printfanstelle der in bashoder zshImplementierung kleinerer Inkonsistenzen bei der Implementierung der printfVarianten auch die Shell und umgeht einen sehr schmutzigen Effekt, wenn in einem deutschen Gebietsschema LC_ALLfestgelegt, aber nicht exportiert wird. Dann verwendet das eingebaute ,und /usr/bin/printfverwendet ....


Siehe auch %gzum Runden auf eine bestimmte Anzahl von signifikanten Stellen.

Volker Siegel
quelle
0

Das Folgende hat bei mir funktioniert.

#!/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
quelle