Hledám způsob, jak říct awk, aby v substituční operaci dělal vysoce přesnou aritmetiku. To zahrnuje čtení pole ze souboru a jeho nahrazení s přírůstkem 1% na této hodnotě. Ztrácím tam však přesnost. Zde je zjednodušená reprodukce problému:
$ echo 0.4970436865354813 | awk "{gsub($1, $1*1.1)}; {print}" 0.546748
Zde mám 16 číslic po desítkové přesnosti, ale awk dává jen šest. Pomocí printf dostávám stejný výsledek:
$ echo 0.4970436865354813 | awk "{gsub($1, $1*1.1)}; {printf("%.16G\n", $1)}" 0.546748
Nějaké návrhy, jak dosáhnout požadované přesnosti?
Komentáře
Odpověď
$ echo 0.4970436865354813 | awk -v CONVFMT=%.17g "{gsub($1, $1*1.1)}; {print}" 0.54674805518902947
Nebo spíše zde:
$ echo 0.4970436865354813 | awk "{printf "%.17g\n", $1*1.1}" 0.54674805518902947
je pravděpodobně to nejlepší, co můžete dosáhnout. Místo toho použijte bc
pro libovolnou přesnost.
$ echo "0.4970436865354813 * 1.1" | bc -l .54674805518902943
Komentáře
- Pokud chcete v
AWK
libovolnou přesnost, můžete použít příznak-M
a nastavitPREC
hodnotu na velké číslo - @RobertBenson, pouze s GNU awk a pouze s nejnovějšími verzemi (4.1 nebo vyšší, takže ne v době, kdy byla odpověď napsána) a pouze tehdy, když bylo při kompilaci povoleno MPFR čas.
Odpověď
Pro vyšší přesnost s (GNU) awk (s kompilací bignum) použijte:
$ echo "0.4970436865354813" | awk -M -v PREC=100 "{printf("%.18f\n", $1)}" 0.497043686535481300
PREC = 100 znamená 100 bitů místo výchozích 53 bitů.
Pokud tento awk není k dispozici, použijte bc
$ echo "0.4970436865354813*1.1" | bc -l .54674805518902943
Nebo se budete muset naučit žít s vrozenou nepřesností plováků.
V původních řádcích existuje několik problémů:
- Faktor 1,1 je 10% nárůst, ne 1% (mělo by být a Multiplikátor 1,01). Použiji 10%.
-
Formát převodu z řetězce na (plovoucí) číslo je dán programem CONVFMT. Jeho výchozí hodnota je
%.6g
. To omezuje hodnoty na 6 desetinných míst (za tečkou). To se použije na výsledek změny gsub$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
-
Formát printf
g
odstraní koncové nuly:$ 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
Oba problémy lze vyřešit pomocí:
$ echo "$a" | awk "{printf("%.17g\n", $1*1.1)}" 0.54674805518902947
Nebo
$ echo "$a" | awk -v CONVFMT=%.30g "{gsub($1, $1*1.1)}; {printf("%.17f\n", $1)}" 0.54674805518902947
Ale nechápejte, že to znamená vyšší přesnost. Interní číselná reprezentace je stále float v dvojnásobné velikosti. To znamená 53 bitů přesnosti a díky tomu si můžete být jisti pouze 15 správnými desetinnými číslicemi, i když mnohokrát až 17 číslic vypadá správně. To „sa mirage.
$ echo "$a" | awk -v CONVFMT=%.30g "{gsub($1, $1*1.1}; {printf("%.30f\n", $1)}" 0.546748055189029469325134868996
Správná hodnota je:
$ echo "scale=18; 0.4970436865354813 * 1.1" | bc .54674805518902943
Které by mohly počítat také s (GNU) awk, pokud byla knihovna bignum kompilována do:
$ echo "$a" | awk -M -v PREC=100 -v CONVFMT=%.30g "{printf("%.30f\n", $1)}" 0.497043686535481300000000000000
Odpovědět
Můj awk skript je větší než jen jedna linka, proto jsem použil kombinaci odpovědí Stéphana Chazelasa a Isaaca:
- Nastavil jsem
CONVFMT
proměnná, která se globálně postará o výstupní formátování - používám také parametr bignum
-M
spolu sPREC
proměnná
Ukázkový fragment:
#!/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 zjednodušil jeho příklad, ale pokud awk skript není jedna vložka, kterou nechcete znečisťovat printf
s, ale v proměnné nastavte tento formát. Stejně tak přesnost, aby se neztratila při skutečném vyvolání příkazového řádku.
gsub
zbytečné. Problém spočívá v tom, žegsub
funguje na řetězcích, nikoli na číslech, takže převod se nejprve provede pomocíCONVFMT
a výchozí hodnota je%.6g
.