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
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 dePREC
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:
- Ik heb de
CONVFMT
variabele die globaal zorgt voor de uitvoeropmaak - Ik gebruik ook de bignum-parameter
-M
samen met dePREC
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.
gsub
niet nodig. Het probleem is datgsub
werkt op strings, niet op getallen, dus een conversie wordt eerst gedaan metCONVFMT
, en de standaardwaarde daarvoor is%.6g
.