bc: 一个方便的实用工具
Linux,几乎和所有的 UNIX 系统一样,包含了大量的实用工具,它们隐藏在诸如 /usr/bin 和 /usr/local/bin 这样的地方。其中一个就是 GNU 实用工具 bc。
bc 是一种任意精度计算器语言。它可以执行任意精度的算术运算(包括整数和实数),并且支持简单的编程。它通过以下命令启动
bc -l files
可选的 -l 标志加载一个数学库,files(也是可选的)是一个包含 bc 命令的文件列表。还有一些其他的标志,但它们并没有极大地改变功能。数学库使以下函数可用于 bc
s(x): x 的正弦值,单位为弧度
c(x): x 的余弦值,单位为弧度
a(x): x 的反正切值(结果以弧度返回。)
l(x): x 的自然对数
e(x): 指数函数 ex
j(n,x): 阶数为 n 的 x 的贝塞尔函数
让我们看看 bc 在运行中的一些例子,假设它已经用 -l 标志启动
2^400 2582249878086908589655919172003011874329705792829\ 2235128306593565406476220168411946296453532801378\ 31435903171972747493376 scale=50 pi=4*a(1) e(pi*sqrt(163)) 262537412640768743.999999999999250072597198185688\ 78393709875517366778 scale=100 l(2) .693147180559945309417232121458176568075500134360\ 2552541206800094933936219696947156058633269964186\ 875
值 scale 是 bc 的内部变量之一:它给出了小数点右边的位数。其他版本的 bc 不允许 scale 的任意值。如果我们想要更多的小数位数,我们可以轻松地在下面的例子中使用 1000 而不是 10。
scale=10 4*a(1) 3.1415926532在我的电脑,一台 Pentium 133 上,计算 pi 到 1000 位小数大约需要一分半钟才能完成。
bc 提供了大多数标准的算术运算
scale=0 920^17%2773 948 .^157%2773 920
句点 (.) 是最后一个结果的简写。百分号 % 是余数函数;如果 scale 设置为零,它会产生标准的整数余数。当使用 -l 标志调用 bc 时,scale 的值被设置为 20。
bc 中的语句会尽快计算。因此,当像上面那样交互式地使用 bc 时,语句会在键入后立即被评估。bc 中的程序只是一个要评估的语句列表。该编程语言提供了循环、分支和递归,其语法类似于 C 语言。一个简单的例子(来自手册页)是阶乘函数
define f(x) { if (x <= 1) return (1); return (x*f(x-1)); }
将这些定义放在一个文件(比如名为 things.b)中,并使用以下命令将其读入 bc 是很方便的
bc -l things.b然后,bc 的输出是
f(150)
5713383956445854590478932865261054003189553578601\ 1264182548375833179829124845398393126574488675311\ 1453771078787468542041626662501986845044663559491\ 9592206657494259209573577892932535729044496247240\ 5416790722118445437122269675520000000000000000000\ 000000000000000000我们可以轻松地编写小程序来计算二项式系数
define b1(n,k) { if (k==0 || k==n) return (1); return (b1(n-1,k)+b1(n-1,k-1)); }这是一个相当低效的程序。解决方案
b1(20,10) 184756需要一些时间来计算。当然,我们可以编写一个更快的程序
define b2(n,k) { auto temp temp=1; if (k==0) return (1); for(i=1; i<=k; i++) temp=temp*(n+1-i)/i; return (temp); }这里 auto 是当前程序本地变量的列表。玩弄这两个计算二项式系数的实现方式是很有启发性的:b2 几乎立即给出结果,而对于所有但非常小的 n 和 k 值,b1 都非常慢。bc 也支持数组;在这里我们使用数组来计算 Hofstadter 混沌函数的首 100 个值
h[1]=1 h[2]=1 for (i=3;i<=100;i++) h[i]=h[i-h[i-1]]+h[i-h[i-2]] h[10] 6 h[50] 25然后我们可以打印出所有这些值
for (i=1; i<=100; i++) { print h[i]," "; if (i%10==0) print "\n;" } 1 1 2 3 3 4 5 5 6 6 6 8 8 8 10 9 10 11 11 12 12 12 12 16 14 14 16 16 16 16 20 17 17 20 21 19 20 22 21 22 23 23 24 24 24 24 24 32 24 25 30 28 26 30 30 28 32 30 32 32 32 32 40 33 31 38 35 33 39 40 37 38 40 39 40 39 42 40 41 43 44 43 43 46 44 45 47 47 46 48 48 48 48 48 48 64 41 52 54 56我们看到 bc 特别适合于原型化简单的数值算法。再举两个最后的例子:计算亲和数和用于数值积分的辛普森法则。首先,如果两个整数中的每一个都等于另一个整数的约数之和,则这两个整数是亲和的
scale=0 define sf(n) { auto sum,s; sum=1; s=sqrt(n); for (i=2;i<=s;i++) if (n%i==0) sum=sum+i+n/i; if (s*s==n) sum=sum-s; return (sum); } define amicable(m) { for (j=1;j<=m;j++) if (sf(sf(j))==j && sf(j)!=j && j<sf(j)) print j," ",sf(j),"\n"; print "Done.\n"; }然后,命令 amicable(2000) 将列出所有亲和数对,其中至少有一个小于 2000。
其次,用于数值积分的辛普森法则
define simpson(a,b,n) { auto h,sum_even,sum_odd; h=(b-a)/(2*n); sum_even=0; sum_odd=0; for (i=1;i<=n;i++) sum_odd=sum_odd+f(a+(2*i-1)*h); for(i=1;i<n;i++) sum_even=sum_even+f(a+2*i*h); return ((f(a)+f(b)+4*sum_odd+2*sum_even)*h/3); }
通过例如定义一个函数 f(x)
define f(x) { return (e(-(x^2))); }然后命令
simpson(0,1,10)返回使用 20=2*10 个子区间计算的 f(x) 在 0 和 1 之间积分的辛普森法则的结果。(结果是 .74682418387591474980,精确到小数点后六位。)
在我看来,bc 真的是一个宝藏:它体积小、效率高、自包含且非常实用。它不能被认为是像 C、C++ 或 FORTRAN 这样优秀的快速编程语言的替代品。但作为在用高级语言编码之前快速原型化数值算法的一种手段,它是非常出色的。
