awk високої точності арифметики


11

Я шукаю спосіб сказати awk робити високоточну арифметику в операції заміщення. Це передбачає зчитування поля з файлу та заміну його на 1% з цим значенням. Однак я там втрачаю точність. Ось спрощене відтворення проблеми:

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

Тут у мене 16-кратна цифра після десяткової точності, але awk дає лише шість. Використовуючи printf, я отримую той же результат:

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

Будь-які пропозиції щодо того, як отримати бажану точність?


Можливо, awk має більш високу роздільну здатність, але це просто ваше форматування вихідного сигналу. Використовуйте printf.
сумнівним

Не змінюється значення результату після використання printf. Питання відредаговано відповідно.
mkc

Як зазначав @manatwork, gsubце зайве. Проблема полягає в gsubроботі на рядках, а не на числах, тому перетворення робиться спочатку за допомогою CONVFMT, а значення за замовчуванням - це %.6g.
jw013

@ jw013, Як я вже згадував у запитанні, для моєї оригінальної проблеми потрібен gsub, оскільки мені потрібно замінити число з кроком на 1%. Погоджено, у спрощеному прикладі це не потрібно.
mkc

Відповіді:


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

А точніше тут:

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

це, мабуть, найкраще, що ви можете досягти. Використовуйте bcнатомість для довільної точності.

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

Якщо ви хочете довільної точності, AWKви можете використовувати -Mпрапор і встановити PRECзначення на велику кількість
Роберт Бенсон

3
@RobertBenson, лише з GNU awk і лише з останніми версіями (4.1 або вище, тому не в той час, коли відповідь була написана), і лише тоді, коли MPFR було включено в час компіляції.
Стефан Шазелас

2

Для більшої точності (GNU) awk (із складеним бінтумом) використовуйте:

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

PREC = 100 означає 100 біт замість стандартних 53 біт.
Якщо цього awk недоступний, використовуйте bc

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

Або вам потрібно буде навчитися жити з притаманною неточністю поплавків.


У ваших оригінальних рядках є кілька питань:

  • Коефіцієнт 1,1 - це збільшення на 10%, а не на 1% (має бути множник 1,01). Я буду використовувати 10%.
  • Формат перетворення з рядка в (плаваюче) число задається CONVFMT. Його значення за замовчуванням - %.6g. Це обмежує значення на 6 десяткових цифр (після крапки). Це застосовується до результату зміни 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
  • Формат printf gвидаляє проміжні нулі:

    $ 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

    Обидва питання можна вирішити за допомогою:

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

    Або

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

Але не розумійте, що це означає більш високу точність. Внутрішнє представлення чисел все ще є поплавцем у подвійному розмірі. Це означає, що 53 біти точності, і ви можете бути впевнені лише у 15 правильних десяткових цифрах, навіть якщо багато разів до 17 цифр виглядають правильно. Це міраж.

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

Правильне значення:

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

Що також можна обчислити за допомогою (GNU) awk, якщо бібліотека bignum складена у:

$ echo "$a" | awk -M -v PREC=100 -v CONVFMT=%.30g '{printf("%.30f\n", $1)}'
0.497043686535481300000000000000
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.