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
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țiPREC
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:
- Am setat
CONVFMT
variabilă care se ocupă la nivel global de formatarea ieșirii - De asemenea, folosesc parametrul bignum
-M
împreună cuPREC
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ă.
gsub
nu este necesar. Problema este căgsub
funcționează pe șiruri, nu pe numere, deci o conversie se face mai întâi folosindCONVFMT
, iar valoarea implicită pentru aceasta este%.6g
.