Ответ 1
Используйте AWK (без bash-isms):
item=30
total=70
percent=$(awk "BEGIN { pc=100*${item}/${total}; i=int(pc); print (pc-i<0.5)?i:i+1 }")
echo $percent
43
Я пытаюсь вычислить процент некоторых элементов в Shell Script. Я хотел бы округлить значение, то есть, если результат равен 59,5, я должен ожидать 60, а не 59.
item=30
total=70
percent=$((100*$item/$total))
echo $percent
Это дает 42.
Но на самом деле результат составляет 42,8, и я бы округлял его до 43. "bc" делает трюк, есть ли способ без использования "bc"?
Я не уполномочен устанавливать новые пакеты. "dc" и "bc" не присутствуют в моей системе. Это должно быть чисто Shell, не может использовать скрипты perl или python либо
Используйте AWK (без bash-isms):
item=30
total=70
percent=$(awk "BEGIN { pc=100*${item}/${total}; i=int(pc); print (pc-i<0.5)?i:i+1 }")
echo $percent
43
Принимая 2 * первоначальный процентный расчет и получая по модулю 2, который обеспечивает прирост округления.
item=30
total=70
percent=$((200*$item/$total % 2 + 100*$item/$total))
echo $percent
43
(проверены с помощью bash, ash, dash и ksh)
Это более быстрая реализация, чем отключение AWK-копроцесса:
$ pa() { for i in 'seq 0 1000'; do pc=$(awk "BEGIN { pc=100*${item}/${total}; i=int(pc); print (pc-i<0.5)?i:i+1 }"); done; }
$ time pa
real 0m24.686s
user 0m0.376s
sys 0m22.828s
$ pb() { for i in 'seq 0 1000'; do pc=$((200*$item/$total % 2 + 100*$item/$total)); done; }
$ time pb
real 0m0.035s
user 0m0.000s
sys 0m0.012s
Сценарий оболочки, совместимый с POSIX, ограничен целочисленной арифметикой с использованием языка оболочки ("требуется только подписанная длинная целочисленная арифметика"), поэтому чистое решение оболочки должно эмулировать арифметику с плавающей запятой:
item=30
total=70
percent=$(( 100 * item / total + (1000 * item / total % 10 >= 5 ? 1 : 0) ))
100 * item/total
дает усеченный результат целочисленного деления в процентах.1000 * item/total % 10 >= 5? 1: 0
1000 * item/total % 10 >= 5? 1: 0
вычисляет первое десятичное место, а если оно равно или больше 5, добавляет 1 к целочисленному результату, чтобы округлить его.$
внутри арифметического расширения $((...))
.Если - в противоречие с предпосылкой вопроса - приемлемо использование внешних коммунальных услуг:
awk
предлагает простое решение, которое, однако, связано с предостережением о том, что оно использует истинные двоичные значения с плавающей запятой двойной точности и поэтому может давать неожиданные результаты в десятичном представлении - например, try printf '%.0f\n' 28.5
, что дает 28
а не ожидаемый 29
):awk -v item=30 -v total=70 'BEGIN { printf "%.0f\n", 100 * item / total }'
-v
используется для определения переменных для awk
скрипта, что позволяет обеспечить чистое разделение между одиночным кадром и, следовательно, литералом awk
скриптом и любыми значениями, переданными ему из оболочки.bc
- это утилита POSIX (поэтому ее можно ожидать на большинстве Unix-подобных платформ) и выполняет арифметику произвольной точности, она неизменно обрезает результаты, так что округление должно выполняться другой утилитой; printf
, однако, хотя это и есть утилита POSIX в принципе, не требуется поддерживать спецификаторы форматирования с плавающей запятой (например, используемые внутри awk
выше), поэтому следующее может работать или не работать (и не стоит проблем, учитывая более простое решение awk
и учитывая, что проблемы точности из-за арифметики с плавающей запятой вернулись на рисунке):# !! This MAY work on your platform, but is NOT POSIX-compliant:
# '-l' tells 'bc' to set the precision to 20 decimal places, 'printf '%.0f\n''
# then performs the rounding to an integer.
item=20 total=70
printf '%.0f\n' "$(bc -l <<EOF
100 * $item / $total
EOF
)"
Следующее основано на моем втором ответе, но с expr и back-ticks, которые (хотя, возможно, и отвратительны для большинства) могут быть адаптированы для работы даже в самых архаичных оболочках изначально:
item=30
total=70
percent='expr 200 \* $item / $total % 2 + 100 \* $item / $total'
echo $percent
43