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
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:
- Ustawiłem
CONVFMT
, która globalnie zajmie się formatowaniem danych wyjściowych - Używam także parametru bignum
-M
wraz zPREC
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ń.
gsub
jest niepotrzebne. Problem polega na tym, żegsub
działa na łańcuchach, a nie na liczbach, więc konwersja jest wykonywana najpierw przy użyciuCONVFMT
, a domyślna wartość to%.6g
.