awk hochpräzise Arithmetik

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, dass gsub für Zeichenfolgen und nicht für Zahlen funktioniert. Daher wird zuerst eine Konvertierung mit CONVFMT 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 Zeichen PREC 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:

  1. Ich habe die CONVFMT Variable, die sich global um die Ausgabeformatierung kümmert
  2. Ich verwende auch den bignum-Parameter -M zusammen mit der PREC 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.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.