awk hochpräzise arithmetik

11

Ich suche nach einer Möglichkeit, awk anzuweisen, in einer Substitutionsoperation hochpräzise Arithmetik durchzufü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 Reproduktion des Problems:

 $ echo 0.4970436865354813 | awk '{gsub($1, $1*1.1)}; {print}'
   0.546748

Hier habe ich eine 16-stellige Nachkommastelle, 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 man die gewünschte Präzision erreicht?

mkc
quelle
Vielleicht hat awk eine höhere Auflösung, aber nur Ihre Ausgabeformatierung wird abgeschnitten. Verwenden Sie printf.
Dubiousjim
Keine Änderung des Ergebniswerts nach Verwendung von printf. Frage entsprechend bearbeitet.
mkc
Wie @manatwork hervorgehoben hat, gsubist dies nicht erforderlich . Das Problem besteht darin, dass gsubZeichenfolgen und keine Zahlen verwendet werden. Daher wird zuerst eine Konvertierung durchgeführt CONVFMT, und der Standardwert dafür ist %.6g.
jw013
@ 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. Im vereinfachten Beispiel ist dies nicht erforderlich.
mkc

Antworten:

12
$ 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 bcstattdessen für eine beliebige Genauigkeit.

$ echo '0.4970436865354813 * 1.1' | bc -l
.54674805518902943
Stéphane Chazelas
quelle
Wenn Sie willkürliche Genauigkeit wünschen AWK, können Sie das -MFlag verwenden und den PRECWert auf eine große Zahl setzen
Robert Benson
3
@RobertBenson, nur mit GNU awk und nur mit neueren Versionen (4.1 oder höher, also nicht zum Zeitpunkt der Antwort) und nur, wenn MPFR zur Kompilierungszeit aktiviert war.
Stéphane Chazelas
2

Für eine höhere Präzision 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 Standardbits von 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 Schwimmern 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 ein Multiplikator von 1,01 sein). Ich werde 10% verwenden.
  • Das Konvertierungsformat von einer Zeichenfolge in eine (schwebende) Zahl wird von CONVFMT angegeben. Der Standardwert ist %.6g. Dadurch werden die Werte auf 6 Dezimalstellen (nach dem Punkt) begrenzt. Dies gilt für das Ergebnis der gsub-Änderung von $1.

    $ 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 gentfernt nachgestellte 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 denken Sie nicht daran, 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 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 ist:

$ echo "scale=18; 0.4970436865354813 * 1.1" | bc
.54674805518902943

Was auch mit (GNU) awk berechnet werden könnte, wenn die Bignumbibliothek kompiliert wurde in:

$ echo "$a" | awk -M -v PREC=100 -v CONVFMT=%.30g '{printf("%.30f\n", $1)}'
0.497043686535481300000000000000
Isaac
quelle