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
AWKpuoi utilizzare il flag-Me impostare ilPRECvalore 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
grimuove 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.54674800000000001Entrambi i problemi potrebbero essere risolti con:
$ echo "$a" | awk "{printf("%.17g\n", $1*1.1)}" 0.54674805518902947Oppure
$ 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
CONVFMTche si occuperà a livello globale della formattazione delloutput - Uso anche il parametro bignum
-Minsieme aPRECvariabile
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.
gsubnon è necessario. Il problema è chegsubfunziona sulle stringhe, non sui numeri, quindi una conversione viene eseguita prima utilizzandoCONVFMTe il valore predefinito è%.6g.