awk aritmetică de înaltă precizie

Caut o modalitate de a spune lui awk să facă aritmetică de înaltă precizie într-o operație de substituție. Aceasta implică citirea unui câmp dintr-un fișier și înlocuirea acestuia cu o creștere de 1% a valorii respective. Cu toate acestea, pierd precizie acolo. Iată o reproducere simplificată a problemei:

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

Aici, am o cifră de 16 după precizie zecimală, dar awk dă doar șase. Folosind printf, obțin același rezultat:

$ echo 0.4970436865354813 | awk "{gsub($1, $1*1.1)}; {printf("%.16G\n", $1)}" 0.546748 

Aveți sugestii despre cum să obțineți precizia dorită?

Comentarii

  • Poate că awk are o rezoluție mai mare, dar ' doar că formatarea dvs. de ieșire este trunchiată. Folosiți printf.
  • Nu există modificări în valoarea rezultatului după utilizarea printf. Întrebarea a fost editată în consecință.
  • După cum a subliniat @manatwork, că gsub nu este necesar. Problema este că gsub funcționează pe șiruri, nu pe numere, deci o conversie se face mai întâi folosind CONVFMT, iar valoarea implicită pentru aceasta este %.6g.
  • @ jw013, După cum am menționat în întrebare, problema mea inițială necesită gsub, deoarece trebuie să înlocuiesc un număr cu un increment de 1%. De acord, în exemplul simplificat, nu este necesar.

Răspuns

$ echo 0.4970436865354813 | awk -v CONVFMT=%.17g "{gsub($1, $1*1.1)}; {print}" 0.54674805518902947 

Sau mai bine zis aici:

$ echo 0.4970436865354813 | awk "{printf "%.17g\n", $1*1.1}" 0.54674805518902947 

este probabil cel mai bun lucru pe care îl puteți obține. Folosiți bc în schimb pentru precizie arbitrară.

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

Comentarii

  • Dacă doriți o precizie arbitrară în AWK puteți utiliza pavilionul -M și setați PREC valoare pentru un număr mare
  • @RobertBenson, numai cu GNU awk și numai cu versiuni recente (4.1 sau mai sus, deci nu în momentul în care a fost scris răspunsul) și numai când MPFR a fost activat la compilare timp însă.

Răspuns

Pentru o precizie mai mare cu awk (GNU) (cu bignum compilat în) utilizați:

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

PREC = 100 înseamnă 100 de biți în loc de 53 de biți impliciți.
Dacă acel awk nu este disponibil, utilizați bc

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

Sau va trebui să învățați să trăiți cu imprecizia inerentă a plutitorilor.


În liniile dvs. originale există mai multe probleme:

  • Un factor de 1.1 este o creștere de 10%, nu de 1% (ar trebui să fie un 1.01 multiplicator). Voi folosi 10%.
  • Formatul de conversie dintr-un șir într-un număr (flotant) este dat de CONVFMT. Valoarea sa implicită este %.6g . Aceasta limitează valorile la 6 cifre zecimale (după punct). Aceasta se aplică rezultatului modificării gsub a $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 
  • Formatul printf g elimină zerourile finale:

    $ 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 

    Ambele probleme ar putea fi rezolvate cu:

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

    Sau

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

Dar nu vă faceți ideea că aceasta înseamnă o precizie mai mare. Reprezentarea internă a numărului este încă un flotor în dimensiune dublă. Asta înseamnă 53 de biți de precizie și cu asta ai putea fi sigur doar de 15 cifre zecimale corecte, chiar dacă de multe ori până la 17 cifre arată corect. Acel „sa mirage.

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

Valoarea corectă este:

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

Care ar putea fi calculat și cu (GNU) awk dacă biblioteca bignum a fost compilată în:

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

Răspuns

Scriptul meu awk este mai mare decât un singur liner, așa că am folosit combinația dintre răspunsurile lui Stéphane Chazelas și Isaac:

  1. Am setat CONVFMT variabilă care se ocupă la nivel global de formatarea ieșirii
  2. De asemenea, folosesc parametrul bignum -M împreună cu PREC variabilă

Exemplu de fragment:

#!/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 și-a simplificat exemplul, dar dacă scriptul awk nu este un singur liner pe care nu doriți să îl poluați cu printf s, dar setați formatul astfel în variabilă. La fel, precizia, astfel încât să nu se piardă în invocarea efectivă a liniei de comandă.

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *