Sto cercando un modo per dire ad awk di eseguire operazioni aritmetiche ad alta precisione in unoperazione di sostituzione. Ciò comporta la lettura di un campo da un file e la sua sostituzione con un incremento dell1% su quel valore. Tuttavia, sto perdendo precisione lì. Ecco una riproduzione semplificata del problema:
$ echo 0.4970436865354813 | awk "{gsub($1, $1*1.1)}; {print}" 0.546748
Qui ho 16 cifre dopo la precisione decimale ma awk ne dà solo sei. Utilizzando printf, ottengo lo stesso risultato:
$ echo 0.4970436865354813 | awk "{gsub($1, $1*1.1)}; {printf("%.16G\n", $1)}" 0.546748
Qualche suggerimento su come ottenere la precisione desiderata?
Commenti
Risposta
$ echo 0.4970436865354813 | awk -v CONVFMT=%.17g "{gsub($1, $1*1.1)}; {print}" 0.54674805518902947
O meglio qui:
$ echo 0.4970436865354813 | awk "{printf "%.17g\n", $1*1.1}" 0.54674805518902947
è probabilmente il meglio che puoi ottenere. Utilizza bc
invece per una precisione arbitraria.
$ echo "0.4970436865354813 * 1.1" | bc -l .54674805518902943
Commenti
- Se desideri una precisione arbitraria in
AWK
puoi utilizzare il flag-M
e impostare ilPREC
valore a un numero elevato - @RobertBenson, solo con GNU awk e solo con versioni recenti (4.1 o successive, quindi non al momento in cui è stata scritta la risposta) e solo quando MPFR era abilitato alla compilazione tempo però.
Risposta
Per una maggiore precisione con (GNU) awk (con bignum compilato) usa:
$ echo "0.4970436865354813" | awk -M -v PREC=100 "{printf("%.18f\n", $1)}" 0.497043686535481300
PREC = 100 significa 100 bit invece dei 53 bit predefiniti.
Se awk non è disponibile, usa bc
$ echo "0.4970436865354813*1.1" | bc -l .54674805518902943
Oppure dovrai imparare a convivere con limprecisione intrinseca dei float.
Nelle tue righe originali ci sono diversi problemi:
- Un fattore di 1,1 è un aumento del 10%, non dell1% (dovrebbe essere a Moltiplicatore 1.01). Userò il 10%.
-
Il formato di conversione da una stringa a un numero (mobile) è dato da CONVFMT. Il suo valore predefinito è
%.6g
. Ciò limita i valori a 6 cifre decimali (dopo il punto). Ciò viene applicato al risultato della modifica gsub di$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
-
Il formato printf
g
rimuove gli zeri finali:$ 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
Entrambi i problemi potrebbero essere risolti con:
$ echo "$a" | awk "{printf("%.17g\n", $1*1.1)}" 0.54674805518902947
Oppure
$ echo "$a" | awk -v CONVFMT=%.30g "{gsub($1, $1*1.1)}; {printf("%.17f\n", $1)}" 0.54674805518902947
Ma non avere lidea che questo significhi una maggiore precisione. La rappresentazione del numero interno è ancora un float di dimensione doppia. Ciò significa 53 bit di precisione e con ciò potresti essere sicuro solo di 15 cifre decimali corrette, anche se molte volte fino a 17 cifre sembrano corrette. Quello “è un miraggio.
$ echo "$a" | awk -v CONVFMT=%.30g "{gsub($1, $1*1.1}; {printf("%.30f\n", $1)}" 0.546748055189029469325134868996
Il valore corretto è:
$ echo "scale=18; 0.4970436865354813 * 1.1" | bc .54674805518902943
Che potrebbe essere calcolato anche con (GNU) awk se la libreria bignum è stata compilata in:
$ echo "$a" | awk -M -v PREC=100 -v CONVFMT=%.30g "{printf("%.30f\n", $1)}" 0.497043686535481300000000000000
Risposta
Il mio script awk è più grande di una riga, quindi ho usato la combinazione delle risposte di Stéphane Chazelas e Isaac:
- Ho impostato
CONVFMT
che si occuperà a livello globale della formattazione delloutput - Uso anche il parametro bignum
-M
insieme aPREC
variabile
Snippet di esempio:
#!/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 ha semplificato il suo esempio, ma se il Lo script awk non è una riga che non vuoi inquinare con printf
, ma imposta il formato in questo modo nella variabile. Allo stesso modo la precisione in modo che non si perda nelleffettiva invocazione della riga di comando.
gsub
non è necessario. Il problema è chegsub
funziona sulle stringhe, non sui numeri, quindi una conversione viene eseguita prima utilizzandoCONVFMT
e il valore predefinito è%.6g
.