Contents

在 Bash 中除法计算不同的舍入方法

1. 概述

我们知道Bash 不能原生地执行浮点运算。然而,有时候,我们在做一些除法计算时,希望对结果进行四舍五入。当然,有不同的舍入要求。

在本教程中,我们将学习如何在 Bash 中执行各种舍入方法。

2. 问题介绍

共有三种标准舍入方法。给定一个实数x

  • 向下取整——输出是小于或等于x 的最大整数
  • 向上取整——结果将是大于或等于x 的最小整数
  • 四舍五入——x 的中间值总是向上取整

举几个例子可以帮助我们快速理解它们:

向下取整 向上取整 四舍五入
4 / 2 = 2 2 2 2
3 / 2 = 1.5 1 2 2
10 / 3 = 3.333… 3 4 3

在本教程中,我们将重点介绍正整数的除法。

接下来,让我们通过示例看看如何在 Bash 中实现每个舍入方法。

3. 向下取整

在 Bash 中进行向下取整很容易。这是因为Bash 会自动按除法四舍五入

接下来,让我们看一些 Bash算术表达式 的例子:

$ echo "floor rounding: 4/2 = $(( 4 / 2 ))"
floor rounding: 4/2 = 2
$ echo "floor rounding: 3/2 = $(( 3 / 2 ))"
floor rounding: 3/2 = 1
$ echo "floor rounding: 10/3 = $(( 10 / 3 ))"
floor rounding: 10/3 = 3

正如我们所见,我们不需要在这里做任何额外的工作。

4. 向上取整

与向下取整不同,我们需要自己实现向上取整。在本节中,我们将介绍两种方法。

4.1. 使用余数

假设我们要在X / Y上应用向上取整。一种想法是检查X / Y的余数:R = X % Y

  • R = 0X可以被Y整除,所以X / Y是结果
  • R > 0:结果将是X / Y + 1

此外,Bash 的算术表达式将True计算为1并将False计算为0

$ echo $(( 1 > 0 ))
1
$ echo $(( -1 > 0 ))
0

因此,我们可以构建一个表达式模式来对除法进行向上取整:

$(( ( X / Y ) + ( X % Y > 0 ) ))

接下来,让我们做一些测试:

$ echo "ceiling rounding: 4/2 = $(( ( 4 / 2 ) + ( 4 % 2 > 0 ) ))"
ceiling rounding: 4/2 = 2
$ echo "ceiling rounding: 3/2 = $(( ( 3 / 2 ) + ( 3 % 2 > 0 ) ))"
ceiling rounding: 3/2 = 2
$ echo "ceiling rounding: 10/3 = $(( ( 10 / 3 ) + ( 10 % 3 > 0 ) ))"
ceiling rounding: 10/3 = 4

正如上面的测试所示,我们得到了预期的结果。

4.2. 使用数学技巧

我们已经学习了如何通过检查余数来对除法应用向上取整。

或者,我们可以用一个小数学技巧来实现:Ceiling( X / Y ) = ( X + Y – 1 ) / Y

现在,让我们首先用我们的例子测试它,看看它是否有效:

$ echo "ceiling rounding: 4/2 = $(( ( 4 + 2 - 1 ) / 2 ))"
ceiling rounding: 4/2 = 2
$ echo "ceiling rounding: 3/2 = $(( ( 3 + 2 - 1 ) / 2 ))"
ceiling rounding: 3/2 = 2
$ echo "ceiling rounding: 10/3 = $(( ( 10 + 3 - 1 ) / 3 ))"
ceiling rounding: 10/3 = 4

很好,我们得到了正确的结果。接下来,让我们了解它为什么起作用

对于除法X / Y,我们有两种情况:

  • X可以被Y整除:X = k * Y(k是整数)
  • X不能被Y整除:X = k * Y + r(kr都是整数,并且0< r < Y

我们来看第一种情况:

  • 我们可以将*(X + Y – 1 ) / Y*拆分为 k * Y / Y + ( Y – 1 ) / Y。
  • 我们了解到 Bash 总是将向下取整应用于除法。
  • 因此,我们有*(Y – 1) / Y = 0*。
  • 此外,我们有 k * Y / Y + ( Y – 1 ) / Y = k * Y / Y = k。k正是我们想要得到的结果。

接下来我们来看第二种情况:

  • 正如我们所知 X = k * Y + r ,我们可以将*(X + Y – 1 ) / Y*写为 (k * Y + r + Y – 1 ) / Y。
  • 同样,我们可以将其拆分为 k * Y / Y + ( r + Y – 1 ) / Y。

第一部分很简单:k * Y / Y = k。现在让我们仔细看看第二部分:

  • 我们知道r是一个整数并且0 < r < Y,所以我们有Y =< ( r + Y – 1 ) < 2Y
  • 因此,我们得到*( r + Y – 1 ) / Y = 1*。
  • 最后,当X不能被Y整除时,( X + Y – 1 ) / Y = k + 1。这是天花板四舍五入后的预期结果。

正如我们所见,两种情况都给出了正确的结果。

5. 四舍五入

5.1. 一个类似的数学技巧

我们已经学会了使用数学进行向上取整。同样,我们可以采用同样的思路进行四舍五入。

由于我们想要进行四舍五入,因此我们将Y / 2 而不是Y – 1添加到X

所以,我们有公式:Half-up( X / Y ) = ( X + Y / 2 ) / Y

接下来,让我们做一些测试:

$ echo "half-up rounding: 4/2 = $(( ( 4 + 2 / 2 ) / 2 ))"
half-up rounding: 4/2 = 2
$ echo "half-up rounding: 3/2 = $(( ( 3 + 2 / 2 ) / 2 ))"
half-up rounding: 3/2 = 2
$ echo "half-up rounding: 10/3 = $(( ( 10 + 3 / 2 ) / 3 ))"
half-up rounding: 10/3 = 3

如上面的输出所示,我们得到了预期的结果。

5.2. 为什么不使用printf “%.0f”

我们在上一节中学习了进行四舍五入的公式。

但是,我们可能会在实践中看到使用printf 命令或printf函数进行半向上舍入的代码。

让我们看几个例子:

$ printf "half-up rounding: 3/2 = %.0f\n" $( echo "scale=2; 3 / 2" | bc)
half-up rounding: 3/2 = 2
$ awk 'BEGIN { printf "half-up rounding: 3/2 = %.0f\n", 3 / 2 }'
half-up rounding: 3/2 = 2

第一个示例使用 bc 命令计算3 / 2并让printf命令进行格式打印。

printf命令遵循 C 的printf函数规范。**格式字符串“ %.0f ”将浮点精度设置为0。即只保留整数部分。**此外,printf将四舍五入到最接近的整数。

第二个 awk示例不执行printf命令。相反,  awk使用它自己的printf语句来进行格式打印。

awkprintf语句遵循与 C 的printf函数相同的规则。因此,它与第一个示例几乎相同。

如上面的输出所示,当我们计算3 / 2时,两个示例都给出了正确答案。

但是,极端情况会破坏这两个命令

$ printf "half-up rounding: 1/2 = %.0f\n" $( echo "scale=2; 1 / 2" | bc)
half-up rounding: 1/2 = 0
$ awk 'BEGIN { printf "half-up rounding: 1/2 = %.0f\n", 1 / 2 }'
half-up rounding: 1/2 = 0

正如上面的测试所示,这一次,我们计算1/2。我们知道结果是0.5。因此,在半舍入之后,我们期望得到1。但是,这两个命令都给出了0

这是因为格式为“%.0f ”的printf会将0.5舍入为0而不是1

$ printf "%.0f\n" 0.5
0

最后,让我们用我们的数学方法测试相同的计算:

$ echo "half-up rounding: 1/2 = $(( ( 1 + 2 / 2 ) / 2 ))"
half-up rounding: 1/2 = 1

如上面的输出所示,( X + Y / 2 ) / Y 方法给出了正确的结果。