awk hög precision aritmetik

Jag letar efter ett sätt att berätta awk att göra hög precision aritmetik i en substitutionsoperation. Detta innebär att du läser ett fält från en fil och ersätter det med 1% steg på det värdet. Men jag tappar precision där. Här är en förenklad återgivning av problemet:

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

Här har jag 16 siffror efter decimalprecision men awk ger bara sex. Med printf får jag samma resultat:

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

Några förslag på hur man får önskad precision?

Kommentarer

  • Kanske har awk högre upplösning men ' är bara din utdataformatering trunkerar. Använd printf.
  • Inga ändringar i resultatvärdet efter användning av printf. Frågan redigerades i enlighet därmed.
  • Som @manatwork har påpekat är gsub onödigt. Problemet är att gsub fungerar på strängar, inte på siffror, så en konvertering görs först med CONVFMT, och standardvärdet för det är %.6g.
  • @ jw013, Som jag nämnde i frågan kräver mitt ursprungliga problem gsub eftersom jag måste ersätta ett tal med 1% steg. Överens i det förenklade exemplet krävs det inte.

Svar

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

Eller snarare här:

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

är förmodligen det bästa du kan uppnå. Använd bc istället för godtycklig precision.

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

Kommentarer

  • Om du vill ha godtycklig precision i AWK kan du använda -M -flaggan och ställa in PREC värde till ett stort antal
  • @RobertBenson, endast med GNU awk och endast med senaste versioner (4.1 eller högre, så inte vid den tidpunkt då svaret skrevs) och bara när MPFR var aktiverat vid kompilering tid dock.

Svar

För högre precision med (GNU) awk (med bignum sammanställt) använd:

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

PREC = 100 betyder 100 bitar istället för standard 53 bitar.
Om det här awk inte är tillgängligt, använd bc

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

Eller så måste du lära dig att leva med den inneboende oprecisionen av flottor.


I dina ursprungliga rader finns det flera problem: p>

  • En faktor på 1,1 är 10% ökning, inte 1% (bör vara a 1,01 multiplikator). Jag använder 10%.
  • Konverteringsformatet från en sträng till ett (flytande) nummer ges av CONVFMT. Standardvärdet är %.6g Det begränsar värdena till 6 decimaler (efter punkten). Detta tillämpas på resultatet av gsub-ändringen 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 tar bort efterföljande nollor:

    $ 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 

    Båda problemen kan lösas 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 får inte idén att detta innebär högre precision. Den interna numreringen är fortfarande en flottör i dubbel storlek. Det betyder 53 bitar av precision och med det kan du bara vara säker på 15 korrekta decimalsiffror, även om många gånger upp till 17 siffror ser korrekt ut. Det ”sa mirage.

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

Det rätta värdet är:

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

Vilket kan beräknas också med (GNU) awk om bignum-biblioteket har sammanställts i:

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

Svar

Mitt awk-skript är större än bara ett liner, så jag använde kombinationen av Stéphane Chazelas och Isaacs svar:

  1. Jag ställde in CONVFMT -variabel som globalt tar hand om utdataformateringen
  2. Jag använder också bignum-parametern -M tillsammans med PREC variabel

Exempelavsnitt:

#!/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 förenklade sitt exempel, men om awk-skript är inte ett liner som du inte vill förorena med printf s, men ställ in formatet så här i variabeln. På samma sätt precision så att den inte går vilse i själva kommandoradsinropet.

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *