awk aritmetica ad alta precisione

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

  • Forse awk ha una risoluzione più alta ma ' è solo che la formattazione delloutput viene troncata. Usa printf.
  • Nessuna modifica nel valore del risultato dopo aver usato printf. Domanda modificata di conseguenza.
  • Come ha sottolineato @manatwork, gsub non è necessario. Il problema è che gsub funziona sulle stringhe, non sui numeri, quindi una conversione viene eseguita prima utilizzando CONVFMT e il valore predefinito è %.6g.
  • @ jw013, Come ho menzionato nella domanda, il mio problema originale richiede gsub poiché devo sostituire un numero con un incremento dell1%. Daccordo, nellesempio semplificato non è richiesto.

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 il PREC 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:

  1. Ho impostato CONVFMT che si occuperà a livello globale della formattazione delloutput
  2. Uso anche il parametro bignum -M insieme a PREC 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.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *