Как округлить числа с плавающей точкой в ​​оболочке?

13

Как правильно округлить числа IEEE 754 с плавающей запятой в командной строке?

Я хочу указать точность выходного числа - количество дробных цифр.

Округление 6.66до точности 1должно дать 6.7, например. Больше в таблице ниже:

Value   Precision  Rounded
6.66    0          7
6.66    1          6.7
6.66    2          6.66
6.66    3          6.660
6.666   3          6.666
6.6666  3          6.667

Он должен использоваться в интерактивной оболочке, но в идеале достаточно надежный для использования в сценариях производственной оболочки.

Volker Siegel
источник
Я бы использовал awk.
изоморфизм

Ответы:

30

Округление чисел с плавающей точкой

Что означает «округление числа с плавающей запятой»?
Это легко, очевидно ... Где моя математическая книга из школы ...

Нет, мы уже знаем, что ничего, связанного с числами с плавающей запятой, не так просто:

Для начала есть несколько режимов округления:

Округление вверх?
Округление вниз?
Округление до нуля?
Округление до ближайшего - связь до четного?
Округление до ближайшего - связи от нуля?

Как обращаться с угловыми корпусами? Как узнать, какие угловые случаи?

Хорошо, похоже, нам лучше использовать реализацию стандарта IEEE 754, и пусть наша система позаботится об этом.

Чтобы округлить число с плавающей запятой в оболочке на основе стандартной арифметики с плавающей запятой, нам нужно выполнить три шага:

  • Преобразуйте входной текст из аргумента командной строки в стандартное число с плавающей запятой.
  • Округлите число с плавающей запятой, используя обычную реализацию IEEE 754.
  • Отформатируйте число как строку для вывода.

Оказывается, что команда оболочки printfможет сделать все это. Он может использоваться для печати чисел в соответствии со спецификацией формата, как описано в man 3 printf. Числа округляются неявным образом стандартным способом, если это требуется для выходного формата:

Команда

Округление xдо pточности цифр с вводом в качестве аргументов командной строки:

printf "%.*f\n" "$p" "$x"

Или в конвейере оболочки, с вводом xна стандартный ввод и в pкачестве аргумента:

echo "$x" | xargs printf "%.*f\n" "$p"

Примеры:

$ printf '%.*f\n' 0 6.66
7
$ printf '%.*f\n' 1 6.66
6.7
$ printf '%.*f\n' 2 6.66
6.66
$ printf '%.*f\n' 3 6.66
6.660
$ printf '%.*f\n' 3 6.666
6.666
$ printf '%.*f\n' 3 6.6666
6.667

Плохие ловушки

Остерегайтесь локали! Он определяет разделитель между целой и дробной частью - .как вы можете ожидать.
Но посмотрите сами, что происходит в немецком языке, например:

$ LC_ALL=de_DE.UTF-8 printf '%.*f\n' 3 6.6666
6,667

Да, все верно 6,667- шесть запятых шесть шесть семь. Это наверняка испортит ваш сценарий.
(Но только для двух клиентов в Германии. За исключением машин разработчика, отлаживающих в настоящее время для этих клиентов.)

Более надежный

Чтобы сделать его более надежным, используйте:

LC_ALL=C /usr/bin/printf "%.*f\n" "$p" "$x"

или

echo "$x" | LC_ALL=C xargs /usr/bin/printf "%.*f\n" "$p"

Это также использует /usr/bin/printfвместо встроенной оболочки bashили zshдля обхода незначительных несоответствий в реализации printfвариантов и предотвращает очень грязный эффект, когда в немецком языке LC_ALLустановлен, но не экспортирован. Затем встроенный использует ,и /usr/bin/printfиспользует ....


См. Также %gдля округления до указанного числа значащих цифр.

Volker Siegel
источник
0

Следующее сработало для меня.

#!/bin/bash
function float() {
bc << EOF
num = $1;
base = num / 1;
if (((num - base) * 10) > 1 )
    base += 1;
print base;
EOF
echo ""
}

float 3.2
xs2rashid
источник