在不使用“bc”的情况下计算Shell脚本中的舍入百分比

时间:2014-06-18 11:34:08

标签: linux shell percentage

我正在尝试计算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脚本

6 个答案:

答案 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,以便将其四舍五入。
  • 请注意, no 需要在算术扩展$内使用$((...))为变量引用添加前缀。

如果 - 与问题的前提相矛盾 - 使用外部实用程序是可以接受的:

  • 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 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.htmlhttp://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) = -1int(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。