Почему деление в bash-арифметике вычисляет большинство процентов как 0?

15

Попытка арифметики bash для скрипта, но $eне обновляется до конца. Выход говорит сам за себя.

max=5
for e in $(seq 1 1 $max); do 
    percent=$(( $e/$max*100 ))
    echo "echo $e / $max : = $percent"
done

Tl; DR: отображение 1..5 в процентах.

Выход :

echo 1 / 5 : = 0
echo 2 / 5 : = 0
echo 3 / 5 : = 0
echo 4 / 5 : = 0
echo 5 / 5 : = 100

Почему это?

Cybex
источник
1
Связанный: Bash выдает только целое число в качестве выходных данных независимо от входных данных при выполнении вычислений (хотя, в отличие от проблемы, описанной здесь, это не может быть хорошо решено путем переупорядочения операций.)
Элия ​​Каган

Ответы:

24

bashне может обрабатывать нецелую арифметику. Это даст вам правильный результат, если все выражения являются целыми числами. Поэтому вам нужно избегать получения нецелого значения где-то в ваших вычислениях.

В вашем случае, когда вы оцениваете 1 / 5и 2 / 5т. Д., Он создает целочисленные нулевые значения в bash, соответствующие некоторым нецелочисленным значениям, и результаты оказываются соответственно равными нулю. Приоритет деления и умножения - это одно и то же, и одни и те же прецедентные операторы всегда выполняются слева направо, когда они помещаются в выражение.

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

$ max=5; for e in $(seq 1 1 $max); do percent=$(( $e*100/$max )); echo "echo $e / $max : = $percent"; done
echo 1 / 5 : = 20
echo 2 / 5 : = 40
echo 3 / 5 : = 60
echo 4 / 5 : = 80
echo 5 / 5 : = 100
souravc
источник
1
Подвыражение 1/5не создает нецелое значение. Он создает целочисленное значение 0, так как результат целочисленного деления 1 на 5 равен 0. Дальнейшие операции успешно используют это значение 0. Это не то, что предполагал OP, но ни одна операция не создает нецелочисленное значение, и ни одна операция не завершается неудачно.
Элия ​​Каган
@EliahKagan спасибо, что указал на этот недостаток. Ред.
19
16

Bash не очень хорошо справляется с такой арифметикой ... Вот ваша проблема:

$ echo $((1/5))
0
$ echo $((2/5))
0
$ echo $((4/5))
0
$ echo $((4/5))
0

Если вам нужно обрабатывать нецелые значения, вы можете использовать bc

$ max=5; for e in $(seq 1 1 "$max"); do percent=$(bc <<< "scale=1 ; $e/$max*100") ; echo "echo $e / $max : = ${percent%.*}"; done
echo 1 / 5 : = 20
echo 2 / 5 : = 40
echo 3 / 5 : = 60
echo 4 / 5 : = 80
echo 5 / 5 : = 100

(спасибо @Arronical за указание, как форматировать вывод в виде целых чисел)

Занна
источник
Я обязательно посмотрю на это, еще раз спасибо!
Cybex
1
Вы можете обрезать .0вывод, изменив $percent свое эхо на ${percent%.*}:)
Arronical
11

В отличие от bash, awk предлагает полную арифметику с плавающей запятой. Например:

$ awk -v max=5 'BEGIN{for (e=1;e<=max;e++) print "echo " e " / " max " : = " 100*e/max}' 
echo 1 / 5 : = 20
echo 2 / 5 : = 40
echo 3 / 5 : = 60
echo 4 / 5 : = 80
echo 5 / 5 : = 100
John1024
источник
5

Пытаться

percent=$(( $e*100/$max ))

:)

Смотрите раздел Арифметическая оценка:

man bash

Поддерживается только целое число.

Альфред
источник