Почему awk не делает сумму ноль, а вместо этого очень маленьким?

У меня есть этот файл, и я хочу суммировать все числа в первом столбце. Легко:

awk '{s+=$1;print $1,s}' file 0.1048 -1.2705 0.4196 -0.8509 0.4196 -0.4313 0.2719 -0.1594 0.0797 -0.0797 0.0797 -5.55112e-17 #Notice this line 

Понимаете, последнее должно быть 0. Я знаю, что e-17 равно нулю, но иногда вывод равен ровно 0. Если это не 0, выход находится в диапазоне от e-15 до e-17 , отрицательно или положительно знак. Чтобы исправить это, я должен использовать абсолютное значение:

 awk '{s+=$1;if (sqrt(s^2)<0.01) s=0;print $1,s}' file 

Вы знаете, почему это происходит?

5 Solutions collect form web for “Почему awk не делает сумму ноль, а вместо этого очень маленьким?”

Ваш вопрос: «Почему это происходит?», Но ваш неявный вопрос (к которому другие обратились): «Как я могу это исправить?» Вы выяснили подход, который вы подняли в комментарии:

Поэтому, если я умножу его на 1000, чтобы устранить эту точку, я могу получить точный результат, не так ли?

Да. Ну, 10000, так как у вас есть четыре знака после запятой. Учти это:

 awk '{ s+=$1*10000; print $1, s/10000 }' 

К сожалению, это не работает, потому что повреждение уже произошло, как только мы интерпретируем токен (строку) как десятичное число. Например, printf "%.20f\n" показывает, что входные данные 0.4157 фактически интерпретируются как 0.41570000000000001394. В этом случае умножение на 10000 дает вам то, что вы ожидаете: 4157. Но, например, 0.5973 = 0,59730000000000005311, и умножая его на 10000, получается 5973.00000000000090949470.

Поэтому вместо этого мы пытаемся

 awk '{ s+=int($1*10000); print $1, s/10000 }' 

для преобразования чисел, которые должны быть «целыми» (например, 5973.00000000000090949470) в соответствующие целые числа (5973). Но это терпит неудачу, потому что иногда ошибка преобразования отрицательна; например, 0.7130 составляет 0,71299999999999996714. И функции int( expr ) awk усекаются (к нулю), а не округляются, поэтому int(7129.99999999) равен 7129.

Итак, когда жизнь дает вам лимоны, вы делаете лимонад. И когда инструмент дает вам функцию усечения, вы округлите, добавив 0.5. 7129.99999999 + 0.5≈7130.49999999, и, конечно, int(7130.49999999) – 7130. Но помните: int() усекает к нулю , а ваш ввод содержит отрицательные числа. Если вы хотите округлить -7129.99999999 до -7130, вам нужно вычесть 0,5 для получения -7130.49999999. Так,

 awk '{ s+=int($1*10000+($1>0?0.5:-0.5)); print $1, s/10000 }' 

который добавляет от -0,5 до $1*10000 если $1 равен ≤ 0.

Это происходит потому, что компьютер имеет ограниченную точность при работе с числами. И доступная точность использует двоичный формат для представления числа.

Это приводит к тому, что числа, которые кажутся тривиальными для записи в нашей десятичной системе, представлены только в качестве приближения (см. Запись в Википедии ): например, 0.1 (как в 1/10 ) действительно хранится как-то как 0.100000001490116119384765625 на компьютере.

Таким образом, все ваши числа действительно обрабатываются только приближением (если вам не повезло и имеют цифры, равные 0.5 которые могут быть представлены точно ).

Суммирование всех этих приблизительных чисел может в конечном итоге привести к ошибке, которая равна != 0 .

Для этого вы можете использовать программу, специально предназначенную для обработки арифметических операций, таких как bc :

 $ awk '{printf "%s + ",$1}' file | sed 's/\+ $/\n/' | bc 0 

Если, как кажется, у вас есть фиксированное число десятичных знаков, вы можете просто удалить их для работы с целыми числами и затем добавить их снова в конце:

 $ awk '{sub("0.","",$1);s+=$1;}END{print s/10000}' file 0 

или

 $ perl -lne 's/0\.//; $s+=$_; END{print $s/10000}' file 0 

Большинство версий awk имеют команду printf . Вместо

 print $1,s 

использование

 printf "%.4f %.4f\n",$1,s 

и выходы будут округлены до 4 знаков после запятой. Таким образом, вы не увидите большинства ошибок округления.

Это не уникальная проблема awk , это еще и проблемы с программированием. Пример с perl :

 $ perl -anle '$sum+=$F[0]}{print $sum' file -5.55111512312578e-17 

Это проблема представления бесконечной серии для базы 2 с использованием конечного числа двоичных цифр. Числа с плавающей запятой не являются целыми числами. Для хранения чисел с плавающей запятой может потребоваться бесконечный объем памяти.

Вы можете прочитать эту статью, чтобы понять больше.

  • Прогулки Многомерные массивы в Mawk
  • Конкатенация колонн горизонтально. Печать только строк, которые пересекаются в 1-й колонке
  • Разделить файл по шаблону, сохраняя только фрагменты, содержащие второй шаблон
  • Добавьте 0, когда значение равно 12 символам
  • Обрезка полей из файла
  • Добавление символа в любую другую текстовую строку
  • Количество отсчетов подстроки в строке
  • Извлечь ключевое слово из строки
  • максимум столбца с совпадающим идентификатором
  • Отсутствует функция float в awk на RHEL 5.8
  • извлечение / копирование диапазонов столбцов из большого файла данных
  • И операция по 2 столбцам (даты доступа и mofidy) в файле в linux
  • Linux и Unix - лучшая ОС в мире.