在 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 = 0:X可以被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(k和r都是整数,并且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语句来进行格式打印。
awk的printf语句遵循与 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 方法给出了正确的结果。