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

Любые предложения о том, как получить желаемую точность?

MKC
источник
Возможно, awk имеет более высокое разрешение, но форматирование вывода усекается. Используйте printf.
dubiousjim
Никаких изменений в значении результата после использования 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 (с компиляцией bignum) используйте:

$ 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
Исаак
источник