Странное поведение округления чисел с плавающей запятой с printf

Подсказка

Каждое поле в a /etc/passwd строка разделяется двоеточием, и имя пользователя является первым полем, таким образом, необходимо отфильтровать каждую строку, чтобы только показать символам до первого двоеточия

Ответ

grep даже не близко к лучшему инструменту для того, чтобы сделать это, но если Вы будете обязаны использовать его, то это будет работать:

grep -oE '^[^:]+' /etc/passwd

-o говорит этому только возвращать часть строки, которая соответствует. -E включает расширенные регулярные выражения так + будет работать позже. ^ соответствует началу строки, [^:] соответствия что-либо кроме двоеточия, и + средства как можно больше символов. Таким образом, это будет соответствовать началу каждой строки вплоть до первого двоеточия

Если Вы можете использовать другие инструменты, кроме того, grep, вот другие обычно лучшие способы сделать его:

cut -d: -f1 /etc/passwd
sed 's/:.*//' /etc/passwd
awk -F: '{print $1}' /etc/passwd

Можно перенаправить результаты любого из тех, которые в allusers использование > allusers как Вы имеют в Вашем примере

7
01.11.2015, 17:27
4 ответа

Как и ожидалось, это "округление до четного", или "округление банкира".

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

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

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

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

  • вверх один раз и вниз в следующий.

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

В коде:

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


Варианты:

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

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

Вверх

Если вам нужно "округлить вверх (в сторону +бесконечное)", то вы можете использовать awk:

value=195.5

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

Down

Если вам все-таки нужно "округлить вниз (В сторону -бесконечного)", то вы можете использовать:

value=195.5

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

Обрезать десятичные знаки.

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

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

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

14
27.01.2020, 20:14

Временное изменение режимов округления не такое уж необычное, и это возможно с помощью 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;
}

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

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

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

Но наиболее важным является 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 и используйте, как описано выше.

1
27.01.2020, 20:14

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

Однако, если мы рассмотрим набор двузначных десятичных дробей: 0.00 0.01, 0.02, 0.03 ... 0.98, 0.99, то увидим, что существует 100 значений, 1 - целое число, 49 нужно округлить вверх, 49 - вниз, 1 ( 0.50 ) может пойти в любую сторону. Если мы всегда округляем в большую сторону, то в среднем получаем числа, которые на 0,01 больше.

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

Это меняет смещение от восходящего к равномерному. В большинстве случаев это лучше.

4
27.01.2020, 20:14

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

если [[$ {a # *.} -Ge "5"]]; тогда a = $ (($ {a%. *} + 1)); иначе a = $ {a%.*}; fi

Или замените цифру "5" на любую, например «6».

P.S. относительно проблемы с "." и / или "," в качестве десятичного разделителя (ов), вот простое универсальное решение.

если [[$ {a ## * [.,]} -Ge "5"]]; тогда a = $ (($ {a% [.,] *} + 1)); иначе a = $ {a% [.,] *}; fi

0
27.01.2020, 20:14

Теги

Похожие вопросы