awk høy presisjon aritmetikk

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

  • Kanskje awk har høyere oppløsning, men det ' bare din utdataformatering blir avkortet. Bruk printf.
  • Ingen endringer i resultatverdien etter bruk av printf. Spørsmålet redigeres deretter.
  • Som @manatwork har påpekt, er gsub unødvendig. Problemet er at gsub fungerer på strenger, ikke tall, så en konvertering gjøres først ved å bruke CONVFMT, og standardverdien for det er %.6g.
  • @ jw013, Som jeg nevnte i spørsmålet, krever mitt opprinnelige problem gsub siden jeg må erstatte et tall med 1% inkrement. Avtalt, i det forenklede eksemplet, er det ikke nødvendig.

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

  1. Jeg satte CONVFMT variabel som globalt tar seg av utdataformateringen
  2. Jeg bruker også bignum-parameteren -M sammen med PREC 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.

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert. Obligatoriske felt er merket med *