awk arytmetyka o wysokiej precyzji


11

Szukam sposobu, aby powiedzieć awk, aby wykonywał arytmetykę o wysokiej precyzji w operacji podstawienia. Polega to na odczytaniu pola z pliku i zastąpieniu go przyrostem o 1% tej wartości. Tracę jednak tam precyzję. Oto uproszczona reprodukcja problemu:

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

Tutaj mam 16 cyfr po dokładności dziesiętnej, ale awk daje tylko sześć. Korzystając z printf, otrzymuję ten sam wynik:

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

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


Być może awk ma wyższą rozdzielczość, ale tylko formatowanie wyjściowe jest obcinane. Użyj printf.
dubiousjim

Brak zmian wartości wyniku po użyciu printf. Pytanie odpowiednio zmodyfikowane.
mkc

Jak zauważył @manatwork, nie gsubjest to konieczne. Problem polega gsubna ciągach, a nie na liczbach, więc konwersja jest wykonywana za pomocą CONVFMT, a domyślną wartością jest %.6g.
jw013

@ jw013, jak wspomniałem w pytaniu, mój pierwotny problem wymaga gsub, ponieważ muszę zastąpić liczbę przyrostem 1%. Uzgodnione, w uproszczonym przykładzie, nie jest wymagane.
mkc

Odpowiedzi:


12
$ 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

jest prawdopodobnie najlepszym, co możesz osiągnąć. Użyj bczamiast tego dla dowolnej precyzji.

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

Jeśli chcesz dowolną precyzję AWK, możesz użyć -Mflagi i ustawić PRECwartość na dużą liczbę
Robert Benson

3
@RobertBenson, tylko z GNU awk i tylko z najnowszymi wersjami (4.1 lub nowszymi, więc nie w momencie pisania odpowiedzi) i tylko wtedy, gdy MPFR było włączone w czasie kompilacji.
Stéphane Chazelas,

2

Aby uzyskać większą precyzję dzięki (GNU) awk (z kompilowanym 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

Lub musisz nauczyć się żyć z nieodłączną niedokładnością pływaków.


W twoich oryginalnych wierszach jest kilka problemów:

  • Współczynnik 1,1 to wzrost o 10%, a nie 1% (powinien być mnożnikiem 1,01). Użyję 10%.
  • Format konwersji łańcucha na liczbę zmiennoprzecinkową podaje CONVFMT. Jego wartość domyślna 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 gusuwa zera końcowe:

    $ 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 wyższą precyzję. Wewnętrzna reprezentacja liczb jest wciąż zmienna podwójna. Oznacza to 53 bity precyzji, dzięki czemu można mieć pewność tylko 15 prawidłowych cyfr dziesiętnych, nawet jeśli wiele razy do 17 cyfr wygląda poprawnie. To 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óry można również obliczyć za pomocą (GNU) awk, jeśli skompilowano bibliotekę bignum:

$ echo "$a" | awk -M -v PREC=100 -v CONVFMT=%.30g '{printf("%.30f\n", $1)}'
0.497043686535481300000000000000
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.