Потрясающее поведение округления с печатью

Я прочитал несколько ответов на этом сайте и нашел желательным округление печати.

Однако, когда я использовал его на практике, тонкая ошибка привела меня к следующему поведению:

 $ echo 197.5 | xargs printf '%.0f' 198 $ echo 196.5 | xargs printf '%.0f' 196 $ echo 195.5 | xargs printf '%.0f' 196 

Обратите внимание, что округление 196.5 становится 196.5 196 .

Я знаю, что это может быть какая-то тонкая ошибка с плавающей запятой (но это не очень большое число, а?), Так кто-то может немного рассказать об этом?

Обходной путь для этого также очень приветствуется (потому что я пытаюсь заставить это работать сейчас).

4 Solutions collect form web for “Потрясающее поведение округления с печатью”

Это как и ожидалось, оно «округло к четному», или «округление банкира».

Ответ на соответствующий сайт объясняет это.

Проблема, которую пытается решить такое правило, заключается в том, что (для чисел с одним десятичным числом)

  • x.1 до x.4 округляются вниз.
  • x.6 до x.9 округляются.

Это 4 и 4.
Чтобы сохранить округление в равновесии, нам нужно округлить x5

  • один раз и вниз .

Это делается по правилу: «От круглого до ближайшего« четного числа »».

В коде:

sh LC_NUMERIC=C printf '%.0f ' "$value"
awk echo "$value" | awk 'printf( "%s", $1)' echo "$value" | awk 'printf( "%s", $1)'


Опции:

В общей сложности существует четыре возможных способа округления числа:

  1. Уже объясненное правило банкира.
  2. Круглый к + бесконечности. Раунд вверх (для положительных чисел)
  3. Круглый к-концу. Раунд вниз (для положительных чисел)
  4. Круглый к нулю. Удалите десятичные знаки (положительные или отрицательные).

вверх

Если вам нужно «округлить» (в сторону +infinite ) », то вы можете использовать awk:

 value=195.5 

awk echo "$value" | awk '{ printf("%d", $1 + 0.5) }' echo "$value" | awk '{ printf("%d", $1 + 0.5) }'
bc echo "scale=0; ($value+0.5)/1" | bc echo "scale=0; ($value+0.5)/1" | bc

вниз

Если вам нужно «округлить» (« -infinite ), вы можете использовать:

 value=195.5 

awk echo "$value" | awk '{ printf("%d", $1 - 0.5) }' echo "$value" | awk '{ printf("%d", $1 - 0.5) }'
bc echo "scale=0; ($value-0.5)/1" | bc echo "scale=0; ($value-0.5)/1" | bc

Десятичные триммы.

Чтобы удалить десятичные знаки (что-нибудь после точки).
Мы могли бы также напрямую использовать оболочку (работает на большинстве оболочек – это POSIX):

 value="127.54" ### Works also for negative numbers. 

shell echo "${value%%.*}"
awk echo "$value"| awk '{printf ("%d",$0)}' echo "$value"| awk '{printf ("%d",$0)}'
bc echo "scale=0; ($value)/1" | bc echo "scale=0; ($value)/1" | bc

Это не ошибка, это намеренно.
Он делает тип раунда до ближайшего (подробнее об этом позже).
С точно .5 мы можем обойти в любом случае. В школе вы, где, вероятно, говорили округлить, но почему? Потому что тогда вам не нужно проверять больше цифр, например, 3.51 раунд до 4; 3.5 мог пойти эфир, но если мы посмотрим только на первую цифру и округлим .5 вверх, мы всегда будем правы.

Однако, если мы посмотрим на набор из 2-значных десятичных знаков: 0,00 0,01, 0,02, 0,03 … 0,98, 0,99, мы увидим, что существует 100 значений, 1 – целое число, 49 приходится округлять, 49 – округлять , 1 (0,50) может идти эфирным путем. Если мы всегда округляем, мы получаем средние числа, которые слишком малы.

Если мы расширим диапазон до 0 → 9.99, у нас будет 9 дополнительных значений, которые округляются вверх. Таким образом, наше усреднение немного больше, чем ожидалось. Итак, одна попытка исправить это: .5 раундов к четному. Половина времени округляется, половина времени округляется.

Это изменяет предвзятость с высоты, до равномерной. В большинстве случаев это лучше.

Временно изменяющиеся режимы округления не так уж необычны, и это возможно с bin/printf хотя само по себе вам не нужно менять источники.

Вам нужны источники coreutils, я использовал последнюю версию, доступную сегодня, которая была http://ftp.gnu.org/gnu/coreutils/coreutils-8.24.tar.xz .

Распакуйте в каталог по вашему выбору с помощью

tar xJfv coreutils-8.24.tar.xz

Перейдите в каталог-источник

cd coreutils-8.24

Загрузите файл src/printf.c в редактор по вашему выбору и обменивайтесь всей main функцией со следующей функцией, включая обе директивы препроцессора, чтобы включить файлы заголовков math.h и fenv.h Основная функция находится в конце и начинается с int main... и заканчивается в самом конце файла закрывающей скобкой }

 #include <math.h> #include <fenv.h> int main (int argc, char **argv) { char *format; char *rounding_env; int args_used; int rounding_mode; initialize_main (&argc, &argv); set_program_name (argv[0]); setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); exit_status = EXIT_SUCCESS; posixly_correct = (getenv ("POSIXLY_CORRECT") != NULL); // accept rounding modes from an environment variable if ((rounding_env = getenv ("BIN_PRINTF_ROUNDING_MODE")) != NULL) { rounding_mode = atoi(rounding_env); switch (rounding_mode) { case 0: if (fesetround(FE_TOWARDZERO) != 0) { error (0, 0, _("setting rounding mode to roundTowardZero failed")); return EXIT_FAILURE; } break; case 1: if (fesetround(FE_TONEAREST) != 0) { error (0, 0, _("setting rounding mode to roundTiesToEven failed")); return EXIT_FAILURE; } break; case 2: if (fesetround(FE_UPWARD) != 0) { error (0, 0, _("setting rounding mode to roundTowardPositive failed")); return EXIT_FAILURE; } break; case 3: if (fesetround(FE_DOWNWARD) != 0) { error (0, 0, _("setting rounding mode to roundTowardNegative failed")); return EXIT_FAILURE; } break; default: error (0, 0, _("setting rounding mode failed for unknown reason")); return EXIT_FAILURE; } } /* We directly parse options, rather than use parse_long_options, in order to avoid accepting abbreviations. */ if (argc == 2) { if (STREQ (argv[1], "--help")) usage (EXIT_SUCCESS); if (STREQ (argv[1], "--version")) { version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version, AUTHORS, (char *) NULL); return EXIT_SUCCESS; } } /* The above handles --help and --version. Since there is no other invocation of getopt, handle '--' here. */ if (1 < argc && STREQ (argv[1], "--")) { --argc; ++argv; } if (argc <= 1) { error (0, 0, _("missing operand")); usage (EXIT_FAILURE); } format = argv[1]; argc -= 2; argv += 2; do { args_used = print_formatted (format, argc, argv); argc -= args_used; argv += args_used; } while (args_used > 0 && argc > 0); if (argc > 0) error (0, 0, _("warning: ignoring excess arguments, starting with %s"), quote (argv[0])); return exit_status; } 

Запустите ./configure следующим образом.

LIBS=-lm ./configure --program-suffix=-own

Он помещает суффикс в каждую подпрограмму (есть много) на всякий случай, если вы хотите установить их все и не уверены, соответствуют ли они остальной системе. Coreutils не называются основными утилитами без причины!

Но наиболее важным является LIBS=-lm перед линией. Нам нужна математическая библиотека, и эта команда сообщает ./configure чтобы добавить ее в список необходимых библиотек.

Запустить make

make

Если у вас многоядерная / многопроцессорная система, попробуйте

make -j4

где число (здесь «4») должно представлять количество ядер, которые вы готовы сэкономить на этой работе.

Если все пойдет хорошо, у вас есть новый printf int src/printf . Попробуйте:

BIN_PRINTF_ROUNDING_MODE=1 ./src/printf '%.0f\n' 196.5

BIN_PRINTF_ROUNDING_MODE=2 ./src/printf '%.0f\n' 196.5

Обе команды должны отличаться на выходе. Цифры после IN_PRINTF_ROUNDING_MODE означают:

  • 0 Округление до 0
  • 1 Округление по ближайшему номеру (по умолчанию)
  • 2 Округление в сторону положительной бесконечности
  • 3 Округление к отрицательной бесконечности

Вы можете установить целое (не рекомендуется) или просто скопировать файл (рекомендуется переименовать его ранее!) src/printf в каталог в вашем PATH и использовать, как описано выше.

Вы можете сделать следующий короткий один вкладыш, если то, что вы на самом деле хотите, – округлить для x.1 до x.4 и округлить для x.5 до x.9.

if [[ ${a#*.} -ge "5" ]]; then a=$((${a%.*}+1)); else a=${a%.*}; fi

Или измените «5» на то, что вы хотите, например «6».

PS относительно вопроса с "." и / или ",", используемый в качестве разделителя (-ов) после запятой, здесь является простым универсальным решением.

if [[ ${a##*[.,]} -ge "5" ]]; then a=$((${a%[.,]*}+1)); else a=${a%[.,]*}; fi

  • Как избежать ряда подстановочных знаков в сценарии оболочки unix?
  • В командной строке я могу перенаправить вывод в файл WITHOUT>, >> или | операторы?
  • Ошибка перенаправления Sub Shell с переменной (while ...) $ 3> $ testdir / $ testfile.log
  • Есть что-то вроде «xdg-close» - напротив xdg-open?
  • Как выполнить замену на месте, которая создает только резервные копии файлов, которые были изменены?
  • Передача кликов по ссылкам в rxvt скрипту
  • Как сделать секвенцию имен файлов с помощью циклов
  • Как я могу запустить скрипт, когда интернет-соединение вот-вот потеряется?
  • bash script - функция цикла
  • Сервисный скрипт и разделяемые библиотеки
  • Как добавить текущую дату перед расширением файла для нескольких файлов в каталоге?
  • Linux и Unix - лучшая ОС в мире.