awk arytmetyka o wysokiej precyzji

Szukam sposobu, aby powiedzieć awk, aby wykonywał arytmetykę o wysokiej precyzji w operacji podstawiania. Obejmuje to odczytanie pola z pliku i zastąpienie go przyrostem o 1% tej wartości. Jednak tracę tam precyzję. Oto uproszczone odtworzenie problemu:

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

Tutaj mam 16 cyfr po przecinku, ale awk podaje tylko sześć. Używając printf, otrzymuję ten sam wynik:

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

Jakieś sugestie, jak uzyskać pożądaną precyzję?

Komentarze

  • Być może awk ma wyższą rozdzielczość, ale ' to tylko formatowanie wyjściowe. Użyj printf.
  • Brak zmian w wartości wyniku po użyciu printf. Pytanie zostało odpowiednio zredagowane.
  • Jak zauważył @manatwork, gsub jest niepotrzebne. Problem polega na tym, że gsub działa na łańcuchach, a nie na liczbach, więc konwersja jest wykonywana najpierw przy użyciu CONVFMT, a domyślna wartość to %.6g.
  • @ jw013, Jak wspomniałem w pytaniu, mój pierwotny problem wymaga gsub, ponieważ muszę zastąpić liczbę przyrostem o 1%. Zgoda, w uproszczonym przykładzie nie jest to wymagane.

Odpowiedź

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

A raczej tutaj:

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

to prawdopodobnie najlepsze, co możesz osiągnąć. Zamiast tego użyj bc, aby uzyskać dowolną precyzję.

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

Komentarze

  • Jeśli chcesz mieć dowolną precyzję w AWK, możesz użyć flagi -M i ustawić PREC na dużą liczbę
  • @RobertBenson, tylko z GNU awk i tylko z ostatnimi wersjami (4.1 lub nowszymi, więc nie w momencie pisania tej odpowiedzi) i tylko wtedy, gdy MPFR był włączony podczas kompilacji jednak czas.

Odpowiedź

Dla większej precyzji z (GNU) awk (z wkompilowanym bignum) użyj:

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

PREC = 100 oznacza 100 bitów zamiast domyślnych 53 bitów.
Jeśli ten awk nie jest dostępny, użyj bc

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

Albo będziesz musiał nauczyć się żyć z nieodłączną nieprecyzyjnością pływaków.


W twoich oryginalnych wierszach występuje kilka problemów:

  • Współczynnik 1,1 to 10% wzrostu, a nie 1% (powinno być Mnożnik 1,01). Użyję 10%.
  • Format konwersji z ciągu znaków na liczbę (zmiennoprzecinkową) określa CONVFMT. Jego domyślna wartość to %.6g . To ogranicza wartości do 6 cyfr dziesiętnych (po kropce). Jest to stosowane do wyniku zmiany 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 
  • Format printf g usuwa końcowe zera:

    $ 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 problemy można rozwiązać za pomocą:

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

    Lub

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

Ale nie myśl, że oznacza to większą precyzję. Reprezentacja liczb wewnętrznych jest nadal liczbą zmiennoprzecinkową w podwójnym rozmiarze. Oznacza to 53 bity precyzji i dzięki temu możesz być pewien tylko 15 poprawnych cyfr dziesiętnych, nawet jeśli wielokrotnie do 17 cyfr wygląda poprawnie. To „sa miraż”.

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

Prawidłowa wartość to:

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

Która być również obliczane za pomocą (GNU) awk, jeśli biblioteka bignum została skompilowana w:

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

Odpowiedź

Mój skrypt awk jest większy niż tylko jedna linijka, więc użyłem kombinacji odpowiedzi Stéphanea Chazelasa i Isaaca:

  1. Ustawiłem CONVFMT, która globalnie zajmie się formatowaniem danych wyjściowych
  2. Używam także parametru bignum -M wraz z PREC zmienna

Przykładowy 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 uprościł swój przykład, ale jeśli Skrypt awk nie jest jedną linijką, której nie chcesz zanieczyszczać printf s, ale ustaw format w zmiennej taki jak ten. Podobnie dokładność, aby nie zgubić się w rzeczywistym wywołaniu wiersza poleceń.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *