Ich suche nach einer Möglichkeit, awk anzuweisen, hochpräzise Arithmetik in einer Substitutionsoperation auszuführen. Dazu müssen Sie ein Feld aus einer Datei lesen und durch ein Inkrement von 1% für diesen Wert ersetzen. Dort verliere ich jedoch an Präzision. Hier ist eine vereinfachte Wiedergabe des Problems:
$ echo 0.4970436865354813 | awk "{gsub($1, $1*1.1)}; {print}" 0.546748
Hier habe ich eine 16-stellige Dezimalgenauigkeit, aber awk gibt nur sechs. Mit printf erhalte ich das gleiche Ergebnis:
$ echo 0.4970436865354813 | awk "{gsub($1, $1*1.1)}; {printf("%.16G\n", $1)}" 0.546748
Irgendwelche Vorschläge, wie Sie die gewünschte Genauigkeit erzielen können?
Kommentare
- Vielleicht hat awk eine höhere Auflösung, aber ' schneidet nur Ihre Ausgabeformatierung ab. Verwenden Sie printf.
- Keine Änderungen des Ergebniswerts nach Verwendung von printf. Die Frage wurde entsprechend bearbeitet.
- Wie @manatwork hervorgehoben hat, ist
gsub
nicht erforderlich. Das Problem ist, dassgsub
für Zeichenfolgen und nicht für Zahlen funktioniert. Daher wird zuerst eine Konvertierung mitCONVFMT
durchgeführt. Der Standardwert hierfür ist%.6g
. - @ jw013, Wie ich in der Frage erwähnt habe, erfordert mein ursprüngliches Problem gsub, da ich eine Zahl durch ein Inkrement von 1% ersetzen muss. Einverstanden, im vereinfachten Beispiel ist dies nicht erforderlich.
Antwort
$ echo 0.4970436865354813 | awk -v CONVFMT=%.17g "{gsub($1, $1*1.1)}; {print}" 0.54674805518902947
Oder besser gesagt hier:
$ echo 0.4970436865354813 | awk "{printf "%.17g\n", $1*1.1}" 0.54674805518902947
ist wahrscheinlich das Beste, was Sie erreichen können. Verwenden Sie stattdessen bc
für eine beliebige Genauigkeit.
$ echo "0.4970436865354813 * 1.1" | bc -l .54674805518902943
Kommentare
- Wenn Sie eine willkürliche Genauigkeit in
AWK
wünschen, können Sie das Flag-M
verwenden und das ZeichenPREC
Wert auf eine große Zahl - @RobertBenson, nur mit GNU awk und nur mit neueren Versionen (4.1 oder höher, also nicht zum Zeitpunkt des Schreibens dieser Antwort) und nur, wenn MPFR beim Kompilieren aktiviert wurde Zeit jedoch.
Antwort
Für eine höhere Genauigkeit mit (GNU) awk (mit kompiliertem Bignum) verwenden Sie:
$ echo "0.4970436865354813" | awk -M -v PREC=100 "{printf("%.18f\n", $1)}" 0.497043686535481300
PREC = 100 bedeutet 100 Bit anstelle der Standard-53 Bit.
Wenn diese awk nicht verfügbar ist, verwenden Sie bc
$ echo "0.4970436865354813*1.1" | bc -l .54674805518902943
Oder Sie müssen lernen, mit der inhärenten Ungenauigkeit von Floats zu leben.
In Ihren ursprünglichen Zeilen gibt es mehrere Probleme:
- Ein Faktor von 1,1 ist eine Erhöhung um 10%, nicht um 1% (sollte a sein 1,01 Multiplikator). Ich werde 10% verwenden.
-
Das Konvertierungsformat von einer Zeichenfolge in eine (schwebende) Zahl wird von CONVFMT angegeben. Der Standardwert ist
%.6g
Dies begrenzt die Werte auf 6 Dezimalstellen (nach dem Punkt). Dies wird auf das Ergebnis der gsub-Änderung von$1
angewendet.$ a="0.4970436865354813" $ echo "$a" | awk "{printf("%.16f\n", $1*1.1)}" 0.5467480551890295 $ echo "$a" | awk "{gsub($1, $1*1.1)}; {printf("%.16f\n", $1)}" 0.5467480000000000
-
Das printf-Format
g
entfernt nachfolgende Nullen:$ echo "$a" | awk "{gsub($1, $1*1.1)}; {printf("%.16g\n", $1)}" 0.546748 $ echo "$a" | awk "{gsub($1, $1*1.1)}; {printf("%.17g\n", $1)}" 0.54674800000000001
Beide Probleme könnten gelöst werden mit:
$ echo "$a" | awk "{printf("%.17g\n", $1*1.1)}" 0.54674805518902947
Oder
$ echo "$a" | awk -v CONVFMT=%.30g "{gsub($1, $1*1.1)}; {printf("%.17f\n", $1)}" 0.54674805518902947
Aber kommen Sie nicht auf die Idee, dass dies eine höhere Präzision bedeutet. Die interne Zahlendarstellung ist immer noch ein Float in doppelter Größe. Das bedeutet 53 Bit Genauigkeit und damit können Sie nur sicher sein, dass 15 Dezimalstellen korrekt sind, selbst wenn bis zu 17 Stellen oft korrekt aussehen. Das ist ein Trugbild.
$ echo "$a" | awk -v CONVFMT=%.30g "{gsub($1, $1*1.1}; {printf("%.30f\n", $1)}" 0.546748055189029469325134868996
Der richtige Wert lautet:
$ echo "scale=18; 0.4970436865354813 * 1.1" | bc .54674805518902943
Was könnte kann auch mit (GNU) awk berechnet werden, wenn die Bignumbibliothek kompiliert wurde in:
$ echo "$a" | awk -M -v PREC=100 -v CONVFMT=%.30g "{printf("%.30f\n", $1)}" 0.497043686535481300000000000000
Antwort
Mein awk-Skript ist größer als nur ein Einzeiler, daher habe ich die Kombination der Antworten von Stéphane Chazelas und Isaac verwendet:
- Ich habe die
CONVFMT
Variable, die sich global um die Ausgabeformatierung kümmert - Ich verwende auch den bignum-Parameter
-M
zusammen mit derPREC
Variable
Beispiel-Snippet:
#!/usr/bin/awk -M -f BEGIN { FS="<|>" CONVFMT="%.18g" PREC=100 } { if ($2 == "LatitudeDegrees") { CORR = $3 // redacted specific corrections print(" <LatitudeDegrees>" CORR "</LatitudeDegrees>"); } else if ($2 == "LongitudeDegrees") { CORR = $3 // redacted specific corrections print(" <LongitudeDegrees>" CORR "</LongitudeDegrees>"); } else { print($0); } } END { }
OP vereinfachte sein Beispiel, aber wenn die awk script ist kein Einzeiler, den Sie nicht mit printf
s verschmutzen möchten, sondern legen Sie das Format in der Variablen wie folgt fest. Ebenso die Genauigkeit, damit sie beim eigentlichen Befehlszeilenaufruf nicht verloren geht.