我正在尝试计算Shell脚本中某些项目的百分比。我想把这个值四舍五入,也就是说,如果结果是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脚本
答案 0 :(得分:23)
使用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
答案 1 :(得分:8)
取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
答案 2 :(得分:6)
符合POSIX标准的shell脚本使用 shell语言("only signed long integer arithmetic is required")限制为整数算术,因此纯shell 解决方案必须模拟浮点运算:
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
计算第一个小数位,如果它等于或大于5,则将整数结果加1,以便将其四舍五入。$
内使用$((...))
为变量引用添加前缀。如果 - 与问题的前提相矛盾 - 使用外部实用程序是可以接受的:
awk
提供简单解决方案,然而,它带有警告,它使用真正的双精度二进制浮点值因此可能会产生unexpected results in decimal representation - 例如,尝试printf '%.0f\n' 28.5
,这会产生28
而不是预期的29
):awk -v item=30 -v total=70 'BEGIN { printf "%.0f\n", 100 * item / total }'
-v
如何用于定义awk
脚本的变量,这样可以在单引号和 literal < em> awk
脚本以及从 shell 传递给它的任何值。bc
is a POSIX utility(因此可以预期在大多数类Unix平台上出现)并执行任意精度算术,它总是如此截断结果,以便舍入必须由另一个实用程序执行;但是,printf
,即使it is a POSIX utility in principle,不需要支持浮点格式说明符(例如在上面的awk
内使用),所以以下< em>可能工作也可能不工作(考虑到更简单的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
)"
答案 3 :(得分:0)
以下是基于我的第二个答案,但是使用 expr 和 back-ticks - 这些(虽然可能对大多数人来说可能令人憎恶)可以适应甚至工作本机上最古老的贝壳:
item=30
total=70
percent=`expr 200 \* $item / $total % 2 + 100 \* $item / $total`
echo $percent
43
答案 4 :(得分:0)
由于自然限制了正百分比(几乎涵盖了所有应用程序),所以我们有一个简单得多的解决方案:
echo $(( ($item*1000/$total+5)/10 ))
它使用由shell完成的向0的自动截断,而不是像我所建议的Michael Back的第二个答案中那样,将模数取为2。
顺便说一句,对于许多人来说可能很明显,但起初对我而言并不明显,在POSIX中固定了评估代码的过程中向0的截断是固定的,POSIX表示必须遵守C标准进行整数运算>
算术运算符和控制流关键字应实现为 等同于引用的ISO C标准部分中列出的内容,如 选定的ISO C标准操作员和控制流关键字。 -请参阅https://pubs.opengroup.org/onlinepubs/9699919799/
有关C标准,请参见https://mc-stan.org/docs/2_21/functions-reference/int-arithmetic.html或http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf中的6.3.1.4节。第一个参考文献是针对C ++的,但是它当然定义了与第二个参考文献相同的整数算法,第二个参考文献则引用了C99 ISO C标准。
请注意,对正百分比的限制并不意味着我们不能拥有某个百分比,例如80%,即减少了20%。负百分比表示减少幅度超过100%。当然,这是有可能发生的,但在典型的应用程序中不会发生。
根据C标准,要覆盖负百分比,我们必须测试中间值item*1000/$total
是否为负,并且在这种情况下,减去5而不是加5,但是我们失去了简单性。
答案 5 :(得分:0)
这是一个不含模2和纯bash的答案,该百分比负数有效:
echo "$(( 200*item/total - 100*item/total ))"
我们可以定义一个通用整数除法,将其舍入到最接近的整数,然后应用它。
function rdiv {
echo $(( 2 * "$1"/"$2" - "$1"/"$2" ))
}
rdiv "$((100 * $item))" "$total"
通常,要使用从float到int的转换来计算最接近的整数,可以在Java中使用n = (int) (2 * x) - (int) x;
说明(基于二进制扩展):
让fbf(x)
为x
的小数部分的第一位,并保持x
的符号。令int(x)
为整数部分,x
被截断为0,再次保持x
的符号。四舍五入到最接近的整数是
round(x) = fbf(x) + int(x).
例如,如果x = 100*-30/70 = -42.857
,则fbf(x) = -1
和int(x) = -42
。
我们现在可以理解迈克尔·贝克的第二个答案,它基于模2,因为:
fbf(x) = int(2*x) % 2
我们可以在这里理解答案,因为:
fbf(x) = int(2*x) - 2*(int(x))
查看此公式的一种简便方法是在将二进制表示形式向左移动时看到2
的乘积。当我们首先移位x
,然后截断时,我们保留了fbf(x)
位,如果我们先截断然后移位,则会丢失该位。
关键点在于,根据Posix和C标准,shell对0进行“四舍五入”,但我们希望对最接近的整数进行四舍五入。我们只需要找到一个窍门即可,并且不需要模2。