陈颂光
全栈工程师,能够独立开发从解释器到网站和桌面/移动端应用的各类软件。
关注我的 GitHub

命令行计算器概览

当需要做点数值计算时,当然可以用很多种编程语言。但有时我们只想方便一点,于是unix下有一些小计算器。

expr

expr的用法很简单:

expr 表达式

另外,shell命令中也可用$((表达式)),它会被替换为结果。

expr支持下列运算,按优先级递增为:

运算
ARG1 &124; ARG2 ARG1非零非null时ARG1,否则ARG2
ARG1 & ARG2 两个参数都非零非null时ARG1,否则0
ARG1 < ARG2 小于
ARG1 <= ARG2 小于等于
ARG1 = ARG2 等于
ARG1 != ARG2 不等
ARG1 >= ARG2 大于等于
ARG1 > ARG2 大于
ARG1 + ARG2 加法
ARG1 - ARG2 减法
ARG1 * ARG2 乘法
ARG1 / ARG2 除法
ARG1 % ARG2 求余
STRING : REGEXP STRING是否匹配REGEXP,有(时返回(与)间匹配部分或null,否则返回匹配的字符数
match STRING REGEXP 相当于 STRING : REGEXP
substr STRING POS LENGTH STRING由POS(由1开始)的子字符串
length STRING STRING的长度
+ TOKEN 把TOKEN视为字符串,即使它是运算符
( EXPRESSION ) EXPRESSION的值

其中仅当参数都为数值时比较为数值比较,否则为字符串比较。

expr在结果非零非null时返回0,否则返回1。语法错误返回2,其它错误返回3。

dc

dc是桌面计算器(desk calculator)的缩写,它处理后缀表达式(也称逆波兰式),可以完成任意精度的定点(而非浮点)运算,甚至使用宏。

后缀表达式的优点在于计算机容易解读和处理,还省去优先级问题。可惜大家习惯了糟糕的中缀表达式,所以反而觉得dc奇特。

dc显然用栈架构实现,打开dc后每输入一个数值(整数或小数,但不支持指数),就被压入栈中,两个相邻数值用空格或换行分隔。每输入一个被方括号包围的字符串也会被压入栈中。当碰到以下的命令,会作相应操作。

打印命令 用途
p 打印栈顶再换行
n 打印并弹出栈顶
P 弹出栈顶,字符串的话打印之,否则绝对值的整数部分以字节序列形式打印
f 打印栈
算术命令 用途
+ 弹出两个元素并推入它们之和
- 弹出两个元素并推入它们之差
* 弹出两个元素并推入它们之积
/ 弹出两个元素并推入它们之商(先弹出的为除数)
% 弹出两个元素并推入它们之余(先弹出的为除数)
~ 弹出两个元素并推入它们之商和余(先弹出的为除数)
^ 弹出两个元素并推入它们之幂(先弹出的为被指数)
| 幂模(先弹出模、再弹出指数、然后底)
v 弹出栈顶并推入其平方根
栈控制命令 用途
c 清空栈
d 重复压入栈顶
r 交换栈顶和栈顶下面的一个元素

dc提供至少256个内存寄存器,分别以单个字符命名,每个寄存器都是栈。

寄存器命令 用途
sr 弹出栈顶并把它压入寄存器r
lr 把寄存器r的栈顶推入主栈
Sr 弹出栈顶并把它压入寄存器r
Lr 弹出寄存器r的栈顶推入主栈

进制可以从2到16。精度为非负整数,表示保留的小数位个数,默认0即除加减法外结果为整数。

参数命令 用途
i 弹出栈顶用作输入进制
o 弹出栈顶用作输出进制
k 弹出栈顶用作精度
I 把当前输入进制推入栈
O 把当前输出进制推入栈
K 把当前精度推入栈
字符串命令 用途
a 弹出栈顶,若为数值则把其最低字节转换为字符串推入栈,否则推入首个字符
x 弹出栈顶并作为宏执行之
>r 若先弹出的大于接着弹出的,则执行寄存器r的内容
!>r 若先弹出的不大于接着弹出的,则执行寄存器r的内容
<r 若先弹出的小于接着弹出的,则执行寄存器r的内容
!<r 若先弹出的不小于接着弹出的,则执行寄存器r的内容
=r 若先弹出的等于接着弹出的,则执行寄存器r的内容
!=r 若先弹出的不等于接着弹出的,则执行寄存器r的内容
? 从终端读一行并执行之
q 退出宏和,调用它的宏。如果没有宏在执行,退出dc
Q 弹出栈顶并退出那么多层宏执行
状态查询命令 用途
Z 弹出栈顶并推入其不含前导零的位个数(字符串为字符数)
X 弹出栈顶并推入其小数位个数(字符串为0)
z 推入当前栈深
杂项命令 用途
! 把本行余下部分看作系统命令,以 <、 =或>开始的命令名前加空格
# 把本行余下部分看作注释
:r 把数组r的下标为先弹出者的元素设为接着弹出的
;r 弹出栈顶并把数组r以它为下标的元素压入栈

和unix其它小语言一样,紧凑有时导致难读,dc有一个非常著名的例子,是一个用Perl实现的RSA公共密钥算法,广泛发布在签名档和T恤上,以示对美国1995年的限制密码学出口的抗议。

print pack"C*",split/D+/,`echo"16iII*oU@{$/=$z;[(pop,pop,unpack "H*",<>)]}EsMsKsN0[1N*11K[d2%Sa2/d0<x+d*lmLa^*1N%0]dsXx++1M1N/dsM0<J]dsJxp"|dc`

bc

bc则处理中缀表达式,可以完成任意精度的定点(而非浮点)运算,并有d类似C语言的控制结构。但很多方面都可看出其设计不完善。

bc可用//包围注释,或者用#开始行末注释。bc用换行或分号分隔语句。不完整语句会导致自动续行,也可用反斜杠续行。

常量和变量

bc中数值常量可有小数点,数值可以有任意精度,内部用十进制表示。

bc中字符串常量用被双引号包围的字符串表示,可用类似C的转义序列。

bc中变量名从小写字母开始,后接若干小写字母、数字或下划线。变量同时可作一维数组变量(保存一些以数值(可以不是整数)为下标索引的数值,用方括号指定下标)和简单变量(保存一个数)。未赋值的变量的值为0。

特殊变量 用途
scale 运算中保留的小数位数
ibase 输入进制,默认10
obase 输出进制,默认10
last 保存上次打印的数

表达式

和C语言类似,表达式由常量的变量经运算符、括号和函数调用(形如函数名(参数列表),其中数组变量后缀[])组合而成。

运算符优先级从高到低为:

  • 不结合:++ var、– var、var ++、var –
  • 不结合:- expr
  • 右结合:expr ^ expr
  • 左结合:expr * expr、 expr / expr、 expr % expr
  • 左结合:expr + expr、 expr - expr
  • 右结合:var = expr、 var = expr
  • 左结合:expr1 < expr2、 expr1 <= expr2、 expr1 > expr2、 expr1 >= expr2、 expr1 == expr2、 expr1 != expr2
  • 不结合:!expr
  • 左结合:expr && expr
  • 左结合:expr   expr

注意这顺序与C语言不同,如a = 3 < 5会把变量a设为3而非1。另外,数值运算以十进制进行。

标准的函数有:

表达式
length(表达式) 有效位数(小数点后的都视为有效),如length(.000001)为6,length(1935.000)=7
read() 从标准输入读的一个数(小心与代码混淆),如length(.000001)为6,length(1935.000)=3
scale(表达式) 小数点后位数
sqrt(表达式) 平方根

启用-l选项的话还可用以下数学函数:

表达式
s(x) x的正弦
c(x) x的余弦
a(x) x的反正切
l(x) x的自然对数
e(x) $e^x$
j(n,x) x的n阶Bessel函数值

语句

语句 用途  
表达式 求值表达式,非赋值表达式会打印结果  
常量 打印它  
print 逗号分隔的表达式或字符串列表 打印各值  
{ 语句序列 } 复合语句  
if ( expression ) statement1 [else statement2] 与C类似  
while ( expression ) statement   与C类似
for ( [expression1] ; [expression2] ; [expression3] ) statement 与C类似  
break 与C类似  
continue 与C类似  
halt 退出bc  
return 返回0  
return 表达式 与C类似  

另外,在编译时碰到quit语句会退出bc,碰到limits会输出实现限制。

函数定义形如

define 名称 ( 参数列表 ) {
       局部变量列表
       一系列语句
}

其中,参数列表中零个或多个名字由逗号分隔(数组要加后缀[]),参数一般按值传递(除非数组参数加前缀*)。

可选的局部变量列表形如auto 名字,...;,分号也是可选的。由于bc用简单的栈结构实现局部变量,当函数a调用b时,b可以访问a的局部变量。

函数体中常量按调用时的ibase解读。不指定返回值将返回0。

关键词 unix 计算器