awk vysoce přesná aritmetika

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

  • Možná má awk vyšší rozlišení, ale ' je pouze zkráceno vaše výstupní formátování. Použít printf.
  • Po použití printf nedojde ke změnám ve výsledné hodnotě. Otázka byla odpovídajícím způsobem upravena.
  • Jak @manatwork zdůraznil, je gsub zbytečné. Problém spočívá v tom, že gsub funguje na řetězcích, nikoli na číslech, takže převod se nejprve provede pomocí CONVFMT a výchozí hodnota je %.6g.
  • @ jw013, Jak jsem zmínil v otázce, můj původní problém vyžaduje gsub, protože musím nahradit číslo s přírůstkem 1%. Dohodnuto, ve zjednodušeném příkladu to není povinné.

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

  1. Nastavil jsem CONVFMT proměnná, která se globálně postará o výstupní formátování
  2. používám také parametr bignum -M spolu s PREC 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.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *