dc (程序)
dc(desk calculator:桌面计算器)是采用逆波兰表示法的跨平台计算器,它支持任意精度算术[1]。它是Robert Morris在贝尔实验室期间书写的[2],作为最老的Unix实用工具,先于C语言的发明。像那个年代的其他实用工具一样,它有着一组强力的特征和简洁的语法[3][4]。传统上,采用中缀表示法的bc计算器程序是在dc之上实现的。
原作者 | Robert Morris (于AT&T贝尔实验室) Lorinda Cherry |
---|---|
开发者 | 各种开源和商业开发者 |
编程语言 | B |
操作系统 | Unix, 类Unix, Plan 9 |
平台 | 跨平台 |
类型 | 命令 |
许可协议 |
历史
dc是幸存的最老的Unix语言[2]。在贝尔实验室收到第一台PDP-11的时候,用B语言写成的dc是在这个新机器上运行的第一个语言,甚至在汇编器之前[5]。
基本运算
在dc中要做4和5的乘法:
$ dc
4 5 *
p
20
q
这可转译为“把4和5压入栈顶,通过乘法算符,从栈中弹出两个元素,将二者相乘并把结果压回栈顶”。接着使用p
命令打印栈顶的元素。使用q
命令退出此次调用的dc实例。注意数值相互间必须以空白分隔,但某些算符可以不必如此。
还可以用如下命令得到这个结果:
$ dc -e '4 5 * p'
20
$ echo "4 5 * p" |dc
20
$ dc -
4 5*pq
20
$ cat <<EOF > cal.txt
4 5 *
p
EOF
$ dc cal.txt
20
使用命令k
来变更算术精度,它设置算术运算的小数位数。因为缺省精度是0
,例如:
$ dc -e "2 3 / p"
0
通过使用命令k
调整精度,可以产生任意数目的小数数位,例如:
$ dc -e "5 k 2 3 / p"
.66666
dc有科学计算器的基本运算功能,比如求 的值:
$ dc -e "2k 12 _3 4 ^ + 11 / v 22 - p"
-19.10
其中,_
用于输入负数,^
计算幂,v
计算平方根。
使用d
命令复制栈顶元素。使用r
命令对栈顶和仅次栈顶的两个元素进行对换。使用z
命令压入当前栈深度,即执行z
命令前栈中元素的数目。
输入/输出
使用?
命令,从stdin读取一行并执行它。这允许从宏中向用户要求输入,故而此输入必须是语法上正确的,并且这有潜在的安全问题,因为dc的!
命令可以执行任意系统命令。
前面提及过,p
命令打印栈顶元素,带有随后的一个换行。n
命令弹出栈顶元素并输出它,没有尾随换行。f
命令打印整个栈,一项一行。
dc还支持控制输入和输出的基数。i
命令弹出栈顶元素并将它用作输入基数。十六进制数字必须大写以避免和dc命令冲突,输入基数必须在2和16之间,输出基数必须大于等于2。o
命令设置输出基数,要记住输入基数将影响对后面的所有数值的分析,所以通常建议先设置输出基数。例如将二进制转换成十六进制:
$ echo 16o2i 11011110101011011011111011101111p | dc
DEADBEEF
要读取设置的这些数值,K
、I
和O
命令将压入当前精度、输入基数和输出基数到栈顶。
语言特征
除了上述的基本算术和栈操作,dc包括了对宏、条件和存储结果用于以后检索的支持。
寄存器
寄存器在dc中是有着单一字符名字的存贮位置,它可以通过命令来存储和检索,它是宏和条件的底层机制:sc
弹出栈顶元素并将它存储入寄存器c
,而lc
将寄存器c
的值压入栈顶。例如:
3 sc 4 lc * p
寄存器还被当作次要栈,可以使用S
和L
命令在它们和主要栈之间压入和弹出数值。存储栈顶元素到寄存器中并把这个元素留在栈顶,需要联合使用ds
命令。
字符串
字符串是包围在[
和]
之中的字符,可以被压入栈顶和存入寄存器。使用x
命令从栈顶弹出字符串并执行它,使用P
命令从栈顶弹出并打印字符串,无尾随换行。a
命令可以把数值的低位字节转换成ASCII字符,或者在栈顶是字符串时把它替换为这个字符串的第一个字符。此外没有方法去建造字符串或进行字符串操纵。
#
字符开始一个注释直到此行结束。
宏
通过允许寄存器和栈项目像数值一样存储字符串,从而实现了宏。一个字符串可以被打印,也可以被执行,就是说作为dc命令的序列而传递。例如可以把一个宏“加1并接着乘以2”存储到一个寄存器m
中:
[1 + 2 *] sm
通过使用x
命令弹出栈顶的字符串并执行之,如下这样使用存储的宏:
3 lm x p
Q
命令从栈顶弹出一个值作为退出宏的层数,比如2Q
命令退出2层宏,它永不导致退出dc。q
命令退出2层宏,如果宏少于2层则退出dc。
条件
最后提供了有条件执行宏的机制。命令=r
将从栈顶弹出两个值,如果二者相等,则执行存储在寄存器r
中的宏。如下命令序列将在原栈顶元素等于5的条件下打印字符串equal
。
[[equal]p] sm d 5 =m
这里使用了d
命令保留原栈顶元素。其他条件有>
、!>
、<
、!<
、!=
,如果栈顶元素分别大于、不大于(小于等于)、小于、不小于(大于等于)、不等于仅次于栈顶的元素,则执行指定宏。
迭代
通过定义有条件的调用自身的递归宏,迭代也是可行的。一个简单的对栈顶元素的阶乘过程:
# F(x): return x!
# if x-1 > 1
# return x * F(x-1)
# otherwise
# return x
可实现为:
[d1-d1<F*]dsFxp
这里宏中的第一个d
命令相当于分配了一个局部变量。
例子
Unix V7手册页举出的编程实例为打印阶乘n!的前10个值:
$ dc -e "[la1+dsa*pla10>y]sy 0sa1 lyx"
1
2
6
24
120
720
5040
40320
362880
3628800
这个程序实现了For循环,将作为循环体的宏[la1+dsa*pla10>y]
存储在寄存器y
中;把寄存器a
作为循环计数器,设其初始值为0
,将0!的值1
压入栈顶;从寄存器y
中取出宏并执行之。宏中的la1+dsa
将计数器a
的数值加1
,并将这个值留在栈顶;随后*p
从栈中弹出两个元素进行乘法并把结果压入栈中,打印这个结果;随后la10>y
将计数器a
的数值和数值10
压入栈中,判断位于栈顶的10
是否大于计数器的数值,即计数器的数值是否小于10
,弹出二者并在判断成立的条件下再次执行存储在寄存器y
中的宏。计数器a
的数值从0
增加到10
,宏一共被执行了10
次。
参见
引用
- ^ Linux用户命令(User Commands)手册页 : an arbitrary precision calculator –
- ^ 2.0 2.1 Brian Kernighan and Ken Thompson. A nerdy delight for any Vintage Computer Fest 2019 attendee: Kernighan interviewing Thompson about Unix. YouTube. 事件发生在 29m45s. [September 3, 2019]. (原始内容存档于2022-02-01).
- ^ The sources for the manual page for 7th Edition Unix dc. [2020-09-25]. (原始内容存档于2019-09-24).
- ^ Ritchie, Dennis M. The Evolution of the Unix Timesharing System. Sep 1979 [2019-05-31]. (原始内容存档于2010-05-06).
- ^ McIlroy, M. D. A Research Unix reader: annotated excerpts from the Programmer's Manual, 1971–1986 (PDF) (技术报告). CSTR. Bell Labs. 1987 [2019-05-31]. 139. (原始内容存档 (PDF)于2019-11-30).