BASH的内置算术功能仅支持整数:
$ printf '%s\n' "$((10 / 3))"
3
对于涉及非整数的大多数操作,需要使用外部程序,例如bc、AWK或dc:
$ printf 'scale=3; 10/3\n' | bc
3.333
"scale=3"命令通知bc需要保留小数点后三位精度。
使用dc进行相同的示例(逆波兰计算器,比bc更轻量级):
$ printf '3 k 10 3 / p\n' | dc
3.333
k将精度设置为3,p打印栈顶的值并换行。但是栈本身不会改变。
如果要比较非整数的大小(小于或大于),并且使用的是GNU bc,则可以这样做:
# Bash和GNU bc
if (( $(bc <<<'1.4 < 2.5') )); then
printf '1.4 is less than 2.5.\n'
fi
但是,并非所有版本的bc都支持x < y的语法:
# HP-UX 10.20.
imadev:~$ bc <<<'1 < 2'
syntax error on line 1,
如果要具备可移植性,需要更加巧妙的方法:
# POSIX
case $(printf '%s - %s\n' 1.4 2.5 | bc) in
-*) printf '1.4 is less than 2.5\n' ;;
esac
该示例将2.5从1.4中减去,并检查结果的符号。如果结果为负,则第一个数字小于第二个数字。实际上,我们并没有将bc的输出视为数字;我们将其视为字符串,并且仅查看第一个字符。
旧版(Bourne)的解决方案如下:
# Bourne
case "`echo "1.4 - 2.5" | bc`" in
-*) echo "1.4 is less than 2.5";;
esac
还可以使用AWK进行计算,示例如下:
$ awk 'BEGIN {printf "%.3f\n", 10 / 3}'
3.333
在这里,bc和awk的解决方案之间存在微妙但重要的区别:bc从标准输入读取命令和表达式,而awk将表达式作为程序的一部分进行求值。标准输入中的表达式不会被求值,即echo 10/3 | awk '{print $0}'会打印出10/3而不是表达式的求值结果。
ksh93、zsh和yash支持在shell算术中使用非整数。zsh(在zsh/mathfunc模块中)和ksh93还支持一些C99 math.h函数,如sin()或cos(),以及可使用C语法调用的用户定义的数学函数。因此,许多这些计算可以在ksh或zsh中本地完成:
# ksh93/zsh/yash
$ LC_NUMERIC=C; printf '%s\n' "$((3.00000000000/7))"
0.428571428571428571
(ksh93和yash对地域设置敏感。在ksh93中,使用非点的点十进制在不是点的小数分隔符字符的地域设置中将失败,比如德语、西班牙语、法语地域设置...在yash中,地域设置的小数基数仅在算术展开的结果中受到尊重。)
比较两个非整数的相等性可能是不明智的。类似的计算,在纸上你可能期望它们给出相同的结果,但由于舍入/截断和其他问题,稍微不同的非整数数值结果可能会略有差异。如果你想确定两个非整数是否"相同",你可以选择以下方法之一:
- 将它们都四舍五入到所需的精度级别,然后比较四舍五入后的结果是否相等;
- 相减并将差的绝对值与你选择的epsilon值进行比较。
- 确保输出足够的精度以完整表达实际值。最好使用Bash支持的十六进制浮点数字面量。
$ ksh93 -c 'LC_NUMERIC=C printf "%-20s %f %.20f %a\n" "error accumulation:" .1+.1+.1+.1+.1+.1+.1+.1+.1+.1{,,} constant: 1.0{,,}'
error accumulation: 1.000000 1.00000000000000000011 0x1.0000000000000002000000000000p+0
constant: 1.000000 1.00000000000000000000 0x1.0000000000000000000000000000p+0
Bash实际上可以使用printf对非整数进行四舍五入:
# Bash 3.1
# 检查a和b是否接近。
# 将每个数字四舍五入到小数点后两位,并将结果作为字符串进行比较。
a=3.002 b=2.998
printf -v a1 %.2f "$a"
printf -v b1 %.2f "$b"
if [[ $a1 = "$b1" ]]; then
printf 'a和b大致相同\n'
fi
许多看起来涉及非整数运算的问题实际上可以仅使用整数来解决,因此不需要这些工具,例如处理有理数的问题。例如,要检查两个数字x和y是否处于4:3或16:9的比例中,可以使用以下方法:
#Bash
# 变量x和y是整数
if (( (x * 9 - y * 16) == 0 )) ; then
printf '16:9.\n'
elif (( (x * 3 - y * 4) == 0 )) ; then
printf '4:3.\n'
else
printf '既不是16:9也不是4:3。\n'
fi
更详细的测试可以判断比例最接近4:3还是16:9,而无需使用非整数运算。请注意,这个非常简单的例子涉及看似包含非整数数和除法的问题,但是使用整数而没有除法来解决。如果可能的话,将问题转换为整数运算通常比使用非整数运算更高效。
如果您觉得文章内容对你有一点帮助可以关注我,我在头条平台会持续分享更多实用的shell技巧和最佳实践,如果想系统的快速学习shell的各种高阶用法和生产环境避坑指南可以看看《shell脚本编程最佳实践》专栏,专栏里有更多的实用小技巧和脚本代码分享。
本文暂时没有评论,来添加一个吧(●'◡'●)