Jeg leter etter en måte å fortelle awk å gjøre høy presisjon aritmetikk i en substitusjonsoperasjon. Dette innebærer å lese et felt fra en fil og erstatte det med en økning på 1% på den verdien. Imidlertid mister jeg presisjonen der. Her er en forenklet reproduksjon av problemet:
$ echo 0.4970436865354813 | awk "{gsub($1, $1*1.1)}; {print}" 0.546748
Her har jeg 16 sifre etter desimal presisjon, men awk gir bare seks. Ved å bruke printf får jeg det samme resultatet:
$ echo 0.4970436865354813 | awk "{gsub($1, $1*1.1)}; {printf("%.16G\n", $1)}" 0.546748
Noen forslag til hvordan du får ønsket presisjon?
Kommentarer
Svar
$ echo 0.4970436865354813 | awk -v CONVFMT=%.17g "{gsub($1, $1*1.1)}; {print}" 0.54674805518902947
Eller rettere sagt her:
$ echo 0.4970436865354813 | awk "{printf "%.17g\n", $1*1.1}" 0.54674805518902947
er sannsynligvis det beste du kan oppnå. Bruk bc
i stedet for vilkårlig presisjon.
$ echo "0.4970436865354813 * 1.1" | bc -l .54674805518902943
Kommentarer
- Hvis du vil ha vilkårlig presisjon i
AWK
, kan du bruke-M
-flagget og settePREC
verdi til et stort antall - @RobertBenson, bare med GNU awk og bare med nyere versjoner (4.1 eller nyere, så ikke på det tidspunktet svaret ble skrevet) og bare når MPFR ble aktivert ved kompilering tid skjønt.
Svar
For høyere presisjon med (GNU) awk (med bignum kompilert i) bruk:
$ echo "0.4970436865354813" | awk -M -v PREC=100 "{printf("%.18f\n", $1)}" 0.497043686535481300
PREC = 100 betyr 100 bits i stedet for standard 53 bits.
Hvis det ikke er tilgjengelig, bruk bc
$ echo "0.4970436865354813*1.1" | bc -l .54674805518902943
Eller du må lære å leve med den iboende upresisjonen til flyter.
I de opprinnelige linjene er det flere problemer:
- En faktor på 1,1 er 10% økning, ikke 1% (skal være a 1,01 multiplikator). Jeg bruker 10%.
-
Konverteringsformatet fra en streng til et (flytende) nummer er gitt av CONVFMT. Standardverdien er
%.6g
Dette begrenser verdiene til 6 desimaltegn (etter prikken). Dette brukes på resultatet av gsub-endringen av$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
-
Printf-formatet
g
fjerner etterfølgende nuller:$ 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
Begge problemene kan løses med:
$ echo "$a" | awk "{printf("%.17g\n", $1*1.1)}" 0.54674805518902947
Eller
$ echo "$a" | awk -v CONVFMT=%.30g "{gsub($1, $1*1.1)}; {printf("%.17f\n", $1)}" 0.54674805518902947
Men ikke forstå at dette betyr høyere presisjon. Den interne tallrepresentasjonen er fortsatt en flottør i dobbel størrelse. Det betyr 53 bit presisjon, og med det kan du bare være sikker på 15 riktige desimaltall, selv om mange ganger opptil 17 sifre ser riktig ut. At «sa mirage.
$ echo "$a" | awk -v CONVFMT=%.30g "{gsub($1, $1*1.1}; {printf("%.30f\n", $1)}" 0.546748055189029469325134868996
Den riktige verdien er:
$ echo "scale=18; 0.4970436865354813 * 1.1" | bc .54674805518902943
Som kunne beregnes også med (GNU) awk hvis bignum-biblioteket er samlet i:
$ echo "$a" | awk -M -v PREC=100 -v CONVFMT=%.30g "{printf("%.30f\n", $1)}" 0.497043686535481300000000000000
Svar
Mitt awk-skript er større enn bare en liner, så jeg brukte kombinasjonen av Stéphane Chazelas og Isaacs svar:
- Jeg satte
CONVFMT
variabel som globalt tar seg av utdataformateringen - Jeg bruker også bignum-parameteren
-M
sammen medPREC
variabel
Eksempel på kodebit:
#!/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 forenklet hans eksempel, men hvis awk-skript er ikke en liner du ikke vil forurense det med printf
s, men angir formatet slik i variabelen. Likeledes presisjonen slik at den ikke går seg vill i selve kommandolinjeanropet.
gsub
unødvendig. Problemet er atgsub
fungerer på strenger, ikke tall, så en konvertering gjøres først ved å brukeCONVFMT
, og standardverdien for det er%.6g
.