awk hoge precisie rekenkunde

Ik ben op zoek naar een manier om awk te vertellen hoge precisie rekenkundige bewerkingen uit te voeren in een substitutieoperatie. Dit houdt in dat je een veld uit een bestand leest en het vervangt door een verhoging van 1% op die waarde. Ik verlies daar echter precisie. Hier is een vereenvoudigde reproductie van het probleem:

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

Hier heb ik 16 cijfers na de decimale precisie, maar awk geeft er slechts zes. Als ik printf gebruik, krijg ik hetzelfde resultaat:

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

Eventuele suggesties voor het verkrijgen van de gewenste precisie?

Opmerkingen

  • Misschien heeft awk een hogere resolutie, maar het ' s is alleen maar je uitvoeropmaak wordt afgebroken. Gebruik printf.
  • Geen veranderingen in resultaatwaarde na gebruik van printf. Vraag dienovereenkomstig aangepast.
  • Zoals @manatwork heeft opgemerkt, is gsub niet nodig. Het probleem is dat gsub werkt op strings, niet op getallen, dus een conversie wordt eerst gedaan met CONVFMT, en de standaardwaarde daarvoor is %.6g.
  • @ jw013, Zoals ik al zei in de vraag, vereist mijn oorspronkelijke probleem gsub omdat ik een getal moet vervangen met een toename van 1%. Akkoord, in het vereenvoudigde voorbeeld is het niet vereist.

Antwoord

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

Of liever hier:

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

is waarschijnlijk het beste wat je kunt bereiken. Gebruik in plaats daarvan bc voor willekeurige precisie.

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

Reacties

  • Als je willekeurige precisie wilt in AWK, kun je de -M vlag gebruiken en de PREC waarde naar een groot getal
  • @RobertBenson, alleen met GNU awk en alleen met recente versies (4.1 of hoger, dus niet op het moment dat dat antwoord werd geschreven) en alleen wanneer MPFR was ingeschakeld tijdens het compileren tijd echter.

Answer

Voor hogere precisie met (GNU) awk (met bignum erin gecompileerd) gebruik:

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

De PREC = 100 betekent 100 bits in plaats van de standaard 53 bits.
Als die awk niet beschikbaar is, gebruik dan bc

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

Of je zult moeten leren leven met de inherente onnauwkeurigheid van drijvers.


In je originele regels zijn er verschillende problemen:

  • Een factor 1,1 is een toename van 10%, niet 1% (zou een 1,01 vermenigvuldiger). Ik gebruik 10%.
  • Het conversieformaat van een string naar een (zwevend) getal wordt gegeven door CONVFMT. De standaardwaarde is %.6g . Dat beperkt de waarden tot 6 decimale cijfers (na de punt). Dat wordt toegepast op het resultaat van de gsub-wijziging van $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 
  • De printf-indeling g verwijdert volgnullen:

    $ 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 

    Beide problemen kunnen worden opgelost met:

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

    Of

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

Maar begrijp niet dat dit een hogere precisie betekent. De weergave van het interne nummer is nog steeds een vlotter in dubbele grootte. Dat betekent 53 bits precisie en daarmee kon je maar zeker zijn van 15 correcte decimale cijfers, zelfs als vaak tot 17 cijfers er correct uitzien. Dat “een luchtspiegeling.

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

De juiste waarde is:

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

Wat zou worden ook berekend met (GNU) awk als de bignum-bibliotheek is gecompileerd in:

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

Answer

Mijn awk-script is groter dan alleen een one-liner, dus ik heb de combinatie van de antwoorden van Stéphane Chazelas en Isaac gebruikt:

  1. Ik heb de CONVFMT variabele die globaal zorgt voor de uitvoeropmaak
  2. Ik gebruik ook de bignum-parameter -M samen met de PREC variabele

Voorbeeldfragment:

#!/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 vereenvoudigde zijn voorbeeld, maar als de awk-script is niet een one-liner die je niet wilt vervuilen met printf s, maar stel het formaat als volgt in de variabele in. Evenzo de precisie, zodat het niet verloren gaat bij het daadwerkelijk aanroepen van de opdrachtregel.

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *