玩命加载中 . . .

shell计算精度


概述

为何要写这篇文章,因为在上篇文章 stastic ES write speed by shell script有使用shell去尝试统计ES的写入速度,需要精确到毫秒级别的时间差,但是尝试了expr,let,发现不行,而是awk却可以,所以本文汇总一下,以bash为例,shell中的计算精度问题。

实践

bc

bc是比较常用的Linux中计算器了,简单方便,而且是支持浮点的。一般系统不自带,需要额外安装。

root@code80:~# echo 1+2 | bc
3
root@code80:~# echo 1.1*6 | bc
6.6
root@code80:~# echo 5/3 | bc
1
root@code80:~# echo 5/3.0 | bc
1
root@code80:~# echo 5.0/3 | bc
1
root@code80:~# echo 5.1/3 | bc
1
root@code80:~# echo "scale=2;5/3" | bc
1.66
root@code80:~# 
root@code80:~# echo "scale=2;1/2" | bc
.50
root@code80:~# echo "scale=4;1/2" | bc
.5000
root@code80:~# 

这里有个问题,如果整数为0,这个整数不会显示,直接显示小数点,解决方法如下:

root@code80:~# echo "print 0;scale=2;1/2" | bc
0.50
root@code80:~#

expr

不支持浮点计算,即不支持小数,所以也常被用来判断变量内容或者结果是不是非0整数(expr 0的echo $?不是0)。

另外,expr 后面的两个参数与运算符之间,一定要含有空格:

root@code80:~# expr 1+2
1+2
root@code80:~# expr 1 + 2
3
root@code80:~# expr 5/3
5/3
root@code80:~# expr 5 / 3
1
root@code80:~# expr 99 / 3.0
expr: non-integer argument
root@code80:~# expr 99 / 3
33
root@code80:~# expr 0
0
root@code80:~# echo $?
1
root@code80:~# 

$(())

此方法不支持浮点计算。

root@code80:~# echo $((1+2.0))
bash: 1+2.0: syntax error: invalid arithmetic operator (error token is ".0")
root@code80:~# echo $((1+2))
3
root@code80:~# echo $((10/2))
5
root@code80:~# echo $((10.1/2))
bash: 10.1/2: syntax error: invalid arithmetic operator (error token is ".1/2")
root@code80:~#

let

不仅不支持浮点计算,而且还只能赋值,不能直接输出。

root@code80:~# let a=1+2
root@code80:~# echo $a
3
root@code80:~# let b=10/5
root@code80:~# echo $b
2
root@code80:~# let b=10/3
root@code80:~# echo $b
3
root@code80:~# let c=1.1*2
bash: let: c=1.1*2: syntax error: invalid arithmetic operator (error token is ".1*2")
root@code80:~# 

上面的方法,bc一般系统不带需要自行安装,不是很方便,万一系统不联网,又没有安装包,没法使用;其他的几个方法,都不支持浮点数,是否有其他方法支持浮点数的?答案是肯定的,继续往下看。

计算浮点数

这里,使用awk获浮点数。

root@code80:~# echo | awk "{print 10.2/2}"
5.1
root@code80:~# echo | awk "{print 10*2.1}"
21
root@code80:~# echo | awk "{print 10.2*2.1}"
21.42
root@code80:~# echo | awk "{print 10.2/2}"
5.1
root@code80:~# echo | awk "{print 10.2*2.1}"
21.42
root@code80:~# echo | awk "{print 10.2+2.1}"
12.3
root@code80:~# echo | awk "{print 10.2-2.1}"
8.1
root@code80:~# echo | awk '{print 10.2-2.1}'
8.1
root@code80:~# echo | awk '{print 10.2+2.1}'
12.3
root@code80:~# echo | awk '{print 10.2*2.1}'
21.42
root@code80:~# echo | awk '{print 10.2/2.1}'
4.85714
root@code80:~#

将数值参数化试试:

root@code80:~# pam1=10.2
root@code80:~# pam2=2.1
root@code80:~# pam3=2
root@code80:~# echo | awk '{print $pam1/$pam2}'
awk: cmd. line:1: (FILENAME=- FNR=1) fatal: division by zero attempted
root@code80:~# echo | awk "{print $pam1/$pam2}"
4.85714
root@code80:~# echo | awk "{print $pam1*$pam2}"
21.42
root@code80:~# echo | awk "{print $pam1-$pam2}"
8.1
root@code80:~# echo | awk "{print $pam1-$pam2*$pam3}"
6
root@code80:~# echo | awk "{print ($pam1-$pam2)*$pam3}"
16.2
root@code80:~#

说明:

这里awk后的print,在不使用参数/变量情况下,使用单引号和双引号都可以,推荐使用双引号,养成习惯。

这里小数点后的位数不受控制,比如本文示例中计算出来的结果4.85714,小数点后有5位,如果只想保留指定位数的小数点,加上printf:

root@code80:~# echo | awk '{print 10/3}' 
3.33333
root@code80:~# echo | awk '{printf ("%.2f\n",10/3)}' 
3.33
root@code80:~# 

看看参数化后的执行效果:

root@code80:~# echo | awk "{printf ("%.2f\n",$pam1/$pam2)}"
awk: cmd. line:1: {printf (%.2fn,10/3)}
awk: cmd. line:1:          ^ syntax error
root@code80:~# 
root@code80:~# echo | awk '{printf ("%.2f\n",$pam1/$pam2)}'
awk: cmd. line:1: (FILENAME=- FNR=1) fatal: division by zero attempted

报错了,这种情况,建议先在前面的echo中将需要使用的变量输出出来,再进行调用。

root@code80:~# echo $pam1 $pam2 | awk '{{printf ("%.2f\n", $1/$2)}}'
3.33
root@code80:~# 

说明:

上面的命令示例中,保留2位小数,printf默认是保留6位。

如果碰到科学计数类型的爆大数字,会出问题:


t@code80:~# echo "5.9637e+12/100" | bc
(standard_in) 1: syntax error
root@code80:~# 

解决方法如下:

root@code80:~# echo "5.9637e+12/100" | awk '{printf ("%.0f\n",$1)}'
5963700000000
root@code80:~# echo "5.9637e+12/100" | awk '{printf ("%.2f\n",$1)}'
5963700000000.00
root@code80:~# 

文章作者: Gavin Wang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Gavin Wang !
  目录