Diese Antwort auf die erste verknüpfte Frage hat die fast wegwerfbare Zeile am Ende:
Siehe auch %g
zum Runden auf eine bestimmte Anzahl von signifikanten Stellen.
Sie können also einfach schreiben
printf "%.2g" "$n"
(Beachten Sie jedoch den folgenden Abschnitt zu Dezimaltrennzeichen und Gebietsschema, und beachten Sie, dass Nicht-Bash printf
nicht unterstützt werden muss %f
und %g
).
Beispiele:
$ printf "%.2g\n" 76543 0.0076543
7.7e+04
0.0077
Natürlich haben Sie jetzt eher eine Mantissen-Exponenten-Darstellung als eine reine Dezimalzahl. Sie möchten also Folgendes zurückkonvertieren:
$ printf "%0.f\n" 7.7e+06
7700000
$ printf "%0.7f\n" 7.7e-06
0.0000077
Füge das alles zusammen und packe es in eine Funktion:
# Function round(precision, number)
round() {
n=$(printf "%.${1}g" "$2")
if [ "$n" != "${n#*e}" ]
then
f="${n##*e-}"
test "$n" = "$f" && f= || f=$(( ${f#0}+$1-1 ))
printf "%0.${f}f" "$n"
else
printf "%s" "$n"
fi
}
(Hinweis - Diese Funktion ist in einer portablen (POSIX) Shell geschrieben, setzt dies jedoch voraus printf
die Gleitkommakonvertierungen handhabt. Bash verfügt über eine integrierte Funktion printf
, sodass Sie hier in Ordnung sind und die GNU-Implementierung auch funktioniert, so dass die meisten GNUs / Linux-Systeme können Dash problemlos verwenden).
Testfälle
radix=$(printf %.1f 0)
for i in $(seq 12 | sed -e 's/.*/dc -e "12k 1.234 10 & 6 -^*p"/e' -e "y/_._/$radix/")
do
echo $i "->" $(round 2 $i)
done
Testergebnisse
.000012340000 -> 0.000012
.000123400000 -> 0.00012
.001234000000 -> 0.0012
.012340000000 -> 0.012
.123400000000 -> 0.12
1.234 -> 1.2
12.340 -> 12
123.400 -> 120
1234.000 -> 1200
12340.000 -> 12000
123400.000 -> 120000
1234000.000 -> 1200000
Ein Hinweis zu Dezimaltrennzeichen und Gebietsschema
Bei allen obigen Arbeiten wird davon ausgegangen, dass das Radixzeichen (auch als Dezimaltrennzeichen bezeichnet) .
wie in den meisten englischen Ländereinstellungen verwendet wird. Andere Gebietsschemas verwenden ,
stattdessen und einige Shells verfügen über ein integriertes printf
Gebietsschema, das das Gebietsschema berücksichtigt. In diesen Shells müssen Sie möglicherweise festlegen LC_NUMERIC=C
, dass die Verwendung eines .
Radixzeichens erzwungen wird, oder Sie müssen schreiben /usr/bin/printf
, um die Verwendung der integrierten Version zu verhindern. Letzteres wird durch die Tatsache erschwert, dass (zumindest in einigen Versionen) Argumente scheinbar immer mit syntaktisch analysiert .
, aber mit den aktuellen Gebietsschemaeinstellungen gedruckt werden.
%f
/%g
, aber das ist dasprintf
Argument, und man braucht kein POSIXprintf
, um eine POSIX-Shell zu haben. Ich denke, du hättest dort kommentieren statt editieren sollen.printf %g
kann nicht in einem POSIX-Skript verwendet werden. Es ist wahr, es liegt amprintf
Dienstprogramm, aber dieses Dienstprogramm ist in den meisten Shells integriert. Das OP ist als bash markiert, daher ist die Verwendung eines bash shebang eine einfache Möglichkeit, ein printf zu erhalten, das% g unterstützt. Andernfalls müssten Sie eine hinzufügen, sofern Ihr printf (oder das printf, das in Ihremsh
if integriertprintf
ist) das nicht standardmäßige (aber durchaus übliche) Format unterstützt%g
...dash
hab ein eingebautesprintf
(welches unterstützt%g
). Auf GNU-Systemenmksh
ist dies wahrscheinlich die einzige Shell, die heutzutage keine eingebaute hatprintf
.bash
) und einige davon in Notizen verwandelt. Sieht es jetzt richtig aus?TL; DR
Einfach kopieren und die Funktion
sigf
im Abschnitt verwendenA reasonably good "significant numbers" function:
. Es ist geschrieben (wie der gesamte Code in dieser Antwort), um mit Bindestrich zu arbeiten .Es wird die
printf
Annäherung an den ganzzahligen Teil von N mit$sig
Ziffern geben.Über das Dezimaltrennzeichen.
Das erste Problem, das mit printf gelöst werden muss, ist der Effekt und die Verwendung des "Dezimalzeichens", das in den USA ein Punkt und in DE ein Komma ist (zum Beispiel). Dies ist ein Problem, da das, was für ein Gebietsschema (oder eine Shell) funktioniert, bei einem anderen Gebietsschema fehlschlägt. Beispiel:
Eine häufige (und falsche) Lösung besteht darin,
LC_ALL=C
den Befehl printf festzulegen. Dadurch wird die Dezimalstelle jedoch auf einen festen Dezimalpunkt gesetzt. Für Gebietsschemata, bei denen ein Komma (oder ein anderes) das häufig verwendete Zeichen ist, das ein Problem darstellt.Die Lösung besteht darin, im Skript herauszufinden, in welcher Shell das Dezimaltrennzeichen für das Gebietsschema ausgeführt wird. Das ist ganz einfach:
Nullen entfernen:
Dieser Wert wird verwendet, um die Datei mit der Liste der Tests zu ändern:
Das macht die Läufe auf jeder Shell oder jedem Gebietsschema automatisch gültig.
Einige Grundlagen.
Es sollte intuitiv sein, die zu formatierende Zahl mit dem Format
%.*e
oder sogar%.*g
printf auszuschneiden. Der Hauptunterschied zwischen der Verwendung von%.*e
oder%.*g
besteht darin, wie die Ziffern gezählt werden. Einer verwendet die volle Zählung, der andere benötigt die Zählung minus 1:Das funktionierte gut für 4 signifikante Stellen.
Nachdem die Anzahl der Stellen aus der Zahl herausgeschnitten wurde, müssen wir einen zusätzlichen Schritt ausführen, um Zahlen mit Exponenten ungleich 0 (wie oben) zu formatieren.
Das funktioniert einwandfrei. Die Zählung des ganzzahligen Teils (links von der Dezimalstelle) entspricht nur dem Wert des Exponenten ($ exp). Die Anzahl der benötigten Dezimalstellen ist die Anzahl der signifikanten Stellen ($ sig) abzüglich der Anzahl der Stellen, die bereits im linken Teil des Dezimaltrennzeichens verwendet wurden:
Da der integrale Teil des
f
Formats keine Begrenzung hat, muss er nicht explizit deklariert werden, und dieser (einfachere) Code funktioniert:Erster Versuch.
Eine erste Funktion, die dies automatisiert ausführen könnte:
Dieser erste Versuch funktioniert mit vielen Zahlen, schlägt jedoch mit Zahlen fehl, für die die Anzahl der verfügbaren Stellen geringer als die angeforderte signifikante Anzahl und der Exponent kleiner als -4 ist:
Es werden viele Nullen hinzugefügt, die nicht benötigt werden.
Zweiter Versuch.
Um dies zu lösen, müssen wir N des Exponenten und alle nachfolgenden Nullen entfernen. Dann können wir die effektive Länge der verfügbaren Ziffern ermitteln und damit arbeiten:
In diesem Fall wird jedoch Gleitkomma-Mathematik verwendet, und "im Gleitkomma ist nichts einfach": Warum addieren sich meine Zahlen nicht?
Aber nichts in "Fließkomma" ist einfach.
Jedoch:
Warum?:
Außerdem besteht der Befehl
printf
aus vielen Muscheln.Welche
printf
Ausdrucke können sich mit der Shell ändern:Eine einigermaßen gute "signifikante Zahl" -Funktion:
Und die Ergebnisse sind:
quelle
Wenn Sie die Nummer bereits als Zeichenfolge haben, dh als "3456" oder "0.003756", können Sie dies möglicherweise nur mit der Zeichenfolgenmanipulation tun. Das Folgende ist von der Spitze meines Kopfes und nicht gründlich getestet und verwendet sed, aber bedenken Sie:
Wo Sie im Grunde genommen alle "-0.000" Sachen am Anfang entfernen und speichern, dann verwenden Sie einen einfachen Teilstring-Vorgang für den Rest. Eine Einschränkung in Bezug auf das Obige ist, dass mehrere führende Nullen nicht entfernt werden. Ich lasse das als Übung.
quelle