aritmetica awk di alta precisione


11

Sto cercando un modo per dire a Awk di eseguire un'aritmetica di alta precisione in un'operazione di sostituzione. Ciò comporta la lettura di un campo da un file e la sua sostituzione con un incremento dell'1% 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 una precisione decimale di 16 cifre, ma awk ne fornisce solo sei. Usando printf, sto ottenendo 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?


Forse awk ha una risoluzione più alta ma è solo la formattazione dell'output a troncarsi. Usa printf.
dubiousjim,

Nessuna modifica nel valore del risultato dopo aver usato printf. Domanda modificata di conseguenza.
mkc,

Come ha sottolineato @manatwork, gsubnon è necessario. Il problema gsubriguarda le stringhe, non i numeri, quindi una conversione viene eseguita per prima CONVFMTe il valore predefinito è %.6g.
jw013,

@ jw013, Come ho già detto nella domanda, il mio problema originale richiede gsub poiché devo sostituire un numero con un incremento dell'1%. D'accordo, nell'esempio semplificato, non è richiesto.
mkc,

Risposte:


12
$ 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. Utilizzare bcinvece per precisione arbitraria.

$ echo '0.4970436865354813 * 1.1' | bc -l
.54674805518902943

Se vuoi una precisione arbitraria AWK, puoi usare la -Mbandiera e impostare il PRECvalore su un numero elevato
Robert Benson,

3
@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 al momento della compilazione.
Stéphane Chazelas,

2

Per una maggiore precisione con (GNU) awk (con bignum compilato in) usare:

$ echo '0.4970436865354813' | awk -M -v PREC=100 '{printf("%.18f\n", $1)}'
0.497043686535481300

Il PREC = 100 significa 100 bit anziché i 53 bit predefiniti.
Se quel awk non è disponibile, usa bc

$ echo '0.4970436865354813*1.1' | bc -l
.54674805518902943

Oppure dovrai imparare a convivere con l'imprecisione intrinseca dei galleggianti.


Nelle linee originali ci sono diversi problemi:

  • Un fattore di 1,1 è un aumento del 10%, non dell'1% (dovrebbe essere un moltiplicatore di 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ò si applica al risultato del cambio 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.54674800000000001

    Entrambi i problemi potrebbero essere risolti con:

    $ echo "$a" | awk '{printf("%.17g\n", $1*1.1)}'
    0.54674805518902947

    O

    $ echo "$a" | awk -v CONVFMT=%.30g '{gsub($1, $1*1.1)}; {printf("%.17f\n", $1)}'
    0.54674805518902947 

Ma non pensare che questo significhi maggiore precisione. La rappresentazione del numero interno è ancora un float in doppia dimensione. 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. Questo è 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 anche essere calcolato 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.