从数字中获取上限整数(BASH)在Linux中

61

我应该如何做类似这样的事情:

ceiling(N/500)

N代表一个数字。

但在Linux的Bash脚本中


在Unix&Linux上提供了Bash的解决方案:将浮点数转换为比最近整数更高的下一个整数 - Frank Breitling
注意:所有基于shell算术扩展$((...))的答案,在操作数已经是浮点数时会失败。例如,echo $((1.1))会产生如下结果:-bash: 1.1: 语法错误:无效的算术运算符(错误标记为“.1”),甚至在尝试除法之前就会出错。 - Irfy
16个回答

117

为什么使用外部脚本语言?默认情况下,您会得到地板(floor)。要得到天花板(ceil),请执行以下操作:

$ divide=8; by=3; (( result=(divide+by-1)/by )); echo $result
3
$ divide=9; by=3; (( result=(divide+by-1)/by )); echo $result
3
$ divide=10; by=3; (( result=(divide+by-1)/by )); echo $result
4
$ divide=11; by=3; (( result=(divide+by-1)/by )); echo $result
4
$ divide=12; by=3; (( result=(divide+by-1)/by )); echo $result
4
$ divide=13; by=3; (( result=(divide+by-1)/by )); echo $result
5
....

要考虑负数,你可以稍微增加些内容。可能有更干净的方法,但作为入门

$ divide=-10; by=10; neg=; if [ $divide -lt 0 ]; then (( divide=-divide )); neg=1; fi; (( result=(divide+by-1)/by )); if [ $neg ]; then (( result=-result )); fi; echo $result
-1

$ divide=10; by=10; neg=; if [ $divide -lt 0 ]; then (( divide=-divide )); neg=1; fi; (( result=(divide+by-1)/by )); if [ $neg ]; then (( result=-result )); fi; echo $result
1

(编辑将let ...切换为(( ... ))。)


5
不错,通过数学特性的解决方法。 - samwize
4
对负数无效。echo $(((-10+10-1)/10)) 的结果为零,而不是负一。 - Hubert Kario
1
不错。添加了一个例子,可能不是最理想的,但可以解决这个问题。 - Kalle
当你写“默认情况下,您会得到地板”时,您的意思是什么?您能详细说明一下吗? - ColinMaudry
1
@ColinMaudry 整数除法会给你向下取整的值。尝试使用 echo $((7/4))。 - Kalle
请注意,bash 仅支持固定大小的整数; $divide + $by - 1 可能会导致溢出。 - chepner

19

使用天花板函数调用脚本语言。 给出$NUMBER

python -c "from math import ceil; print ceil($NUMBER/500.0)"
或者
perl -w -e "use POSIX; print ceil($NUMBER/500.0), qq{\n}"

1
在Perl中,您会使用ceil($ARGV[0]/$ARGV[1])来使用两个脚本参数吗?然后您将在脚本周围使用单引号。或者,使用双引号,您可以让shell替换其$1、$2。 - Jonathan Leffler
当然可以,<code>perl -w -e 'use POSIX; print ceil($ARGV[0]/$ARGV[1]), qq{\n}' $N 500</code> 也可以,等等。TMTOWTDI。重要的是使用基于标准的ceil实现。 - Josh McFadden
代码标签失败了吗?我在这里是新手。 :( - Josh McFadden
哈哈,是的,你不能在注释中编写代码 :( 但没关系,我更喜欢使用Python :) 谢谢你的代码! - Mint
5
要求的语言是bash,因此应该是一个bash shell脚本,不需要使用任何其他语言。我不认为这应该是最佳答案。 - DCurro
@DCurro,问题已经解决了,尽管当我提出问题时并没有纯Bash的解决方案,所以我选择了对我最有效的那个。 - Mint

13

以下是使用 bc(几乎在任何地方都应该安装)的解决方案:

ceiling_divide() {
  ceiling_result=`echo "($1 + $2 - 1)/$2" | bc`
}

以下是纯Bash语言实现的另一个示例:

# Call it with two numbers.
# It has no error checking.
# It places the result in a global since return() will sometimes truncate at 255.

# Short form from comments (thanks: Jonathan Leffler)
ceiling_divide() {
  ceiling_result=$((($1+$2-1)/$2))
}

# Long drawn out form.
ceiling_divide() {
  # Normal integer divide.
  ceiling_result=$(($1/$2))
  # If there is any remainder...
  if [ $(($1%$2)) -gt 0 ]; then
    # rount up to the next integer
    ceiling_result=$((ceiling_result + 1))
  fi
  # debugging
  # echo $ceiling_result
}

3
可以简化这个表达式,去掉条件语句:ceiling_result=$((($1+$2-1)/$2)) - Jonathan Leffler
@1ch1g0 他在Bash中说。Bash中没有浮点数。他的例子也展示了整数,并且他想要一个整数结果。无论如何,我添加了一个使用bc的解决方案。它可以处理浮点数。 - Harvey

6
您可以使用awk。
#!/bin/bash
number="$1"
divisor="$2"
ceiling() {
  awk -vnumber="$number" -vdiv="$divisor" '
  function ceiling(x){return x%1 ? int(x)+1 : x}
  BEGIN{ print ceiling(number/div) }'
}
ceiling

输出

$ ./shell.sh 1.234 500
1

如果可以选择,您可以使用更好的 shell,例如支持浮点运算的 Zsh。

integer ceiling_result
ceiling_divide() {
  ceiling_result=$(($1/$2))
  echo $((ceiling_result+1))
}

ceiling_divide 1.234 500

当然,ZSH的计算不正确。$1/$2可能是精确的。 - Michaël

4

在数学上,天花板函数可以用地板函数来定义,即 ceiling(x) = -floor(-x)。而且,在将正浮点数转换为整数时,默认使用地板函数。

if [ $N -gt 0 ]; then expr 1 - $(expr $(expr 1 - $N) / 500); else expr $N / 500; fi

参考资料:https://zh.wikipedia.org/wiki/取极值和上枚值


1
虽然这段代码片段可能解决了问题,但它并没有解释为什么或者如何回答这个问题。请在你的代码中包含一个解释,因为这真的有助于提高你的帖子质量。记住,你正在为未来的读者回答问题,而那些人可能不知道你的代码建议的原因。 - Balagurunathan Marimuthu
谢谢你的建议! - Frank R.
1
当数字为整数时不正确...floor(500+1)=floor(501)=501!=ceiling(500) - Martin Janiczek
1
@MartinJaniczek 感谢您指出错误。在重新审查了天花板和地板的定义后,我的答案得到了更新。 - Frank R.

4
如果你已经安装了jq,你可以使用它。它是“用于JSON的sed”,但我发现它对于像这样的简单任务也非常方便。
示例:
$ echo 10.001 | jq '.|ceil'
11

$ jq -n '-10.001 | ceil'
-10

4

Kalle 的优秀回答 上进行进一步说明,下面是一个将算法封装成函数的漂亮示例:

ceildiv() {
    local num=$1
    local div=$2
    echo $(( (num + div - 1) / div ))
}

或者可以写成一行代码:
ceildiv(){ echo $((($1+$2-1)/$2)); }

如果您想更加高级,您可以使用一个更强大的版本来验证输入是否为数字,并且还可以处理负数:
ceildiv() {
    local num=${1:-0}
    local div=${2:-1}
    if ! ((div)); then
        return 1
    fi
    if ((num >= 0)); then
        echo $(( (num + div - 1) / div ))
    else
        echo $(( -(-num + div - 1) / div ))
    fi
}

这里使用了一种针对负数的“伪”向上取整方法,即向最高的绝对值整数,例如 -10 / 3 = -4 而不是应该的 -3,因为 -3 > -4。如果您想要一个“真正”的向上取整,请在else后使用$(( num / div ))

然后像这样使用它:

$ ceildiv 10 3
4
$ ceildiv 501 500
2
$ ceildiv 0 3
0
$ ceildiv -10 1
-10
$ ceildiv -10 3
-4

2
Floor () {
  DIVIDEND=${1}
  DIVISOR=${2}
  RESULT=$(( ( ${DIVIDEND} - ( ${DIVIDEND} % ${DIVISOR}) )/${DIVISOR} ))
  echo ${RESULT}
}
R=$( Floor 8 3 )
echo ${R}

Ceiling () {
  DIVIDEND=${1}
  DIVISOR=${2}
  $(( ( ( ${DIVIDEND} - ( ${DIVIDEND} % ${DIVISOR}) )/${DIVISOR} ) + 1 ))
  echo ${RESULT}
}
R=$( Ceiling 8 3 )
echo ${R}

1
如果您有一个十进制数的字符串表示,bash可以使用printf函数实现向上取整,例如:
$ printf %.4f 0.12345
0.1235

但是如果你需要使用小数进行一些数学运算,你可以使用默认精度为20位小数的bc -l,然后使用printf将结果四舍五入。

printf %.3f $(echo '(5+50*3/20 + (19*2)/7 )' | bc -l)
17.929

printf 四舍五入到最近的整数,如果没有 +/-0.5 的小把戏,这个答案是错误的。 - Irfy

0

如果除法返回一个非浮点数,此函数将不会加1。

function ceiling {
    DIVIDEND=${1}
    DIVISOR=${2}
    if [ $(( DIVIDEND % DIVISOR )) -gt 0 ]; then
            RESULT=$(( ( ( $DIVIDEND - ( $DIVIDEND % $DIVISOR ) ) / $DIVISOR ) + 1 ))
    else
            RESULT=$(( $DIVIDEND / $DIVISOR ))
    fi
    echo $RESULT
}

使用方法如下:

echo $( ceiling 100 33 )
> 4

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接