Dc (程序)

dc（desk calculator：桌面计算器）是采用逆波兰表示法的跨平台计算器，它支持任意精度算术。它是在贝尔实验室工作期间书写的，作为最老的Unix实用工具，先于C语言的发明。像那个年代的其他实用工具一样，它有着一组强力的特征和简洁的语法。传统上，采用中缀表示法的bc计算器程序是在dc之上实现的。
历史.
dc是幸存的最老的Unix语言。在贝尔实验室收到第一台PDP-11的时候，用B语言写成的dc是在这个新机器上运行的第一个语言，甚至在汇编器之前。
基本运算.
在dc中要做4和5的乘法：
$ dc
4 5 *
p
20
q
这可转译为“把4和5压入栈顶，通过乘法算符，从栈中弹出两个元素，将二者相乘并把结果压回栈顶”。接着使用codice_1命令打印栈顶的元素。使用codice_2命令退出此次调用的dc实例。注意数值相互间必须以空白分隔，但某些算符可以不必如此。
还可以用如下命令得到这个结果：
$ dc -e '4 5 * p'
20
$ echo "4 5 * p" | dc
20
$ dc -
4 5 *pq
20
$ cat «EOF &gt; cal.dc
4 5 *
p 
EOF
$ dc cal.dc
20
使用命令codice_3来变更算术精度，它设置算术运算的小数位数。缺省精度是codice_4，例如：
$ dc -e '10946 6765 / p'
1
通过使用命令codice_3调整精度，可以产生任意数目的小数数位，例如：
$ dc -e '7k 10946 6765 / p'
1.6180339
dc有科学计算器的基本运算功能，比如求黄金分割率formula_1的值：
$ dc -e '7k 5 v 1 + 2 / p'
1.6180339
dc将输入数值的前导codice_6识别为负号，命令codice_7计算幂，命令codice_8计算平方根。
codice_9命令用于复制栈顶元素。codice_10命令用于对栈顶和仅次栈顶的两个元素进行对换。codice_11命令用于压入当前栈深度，即执行codice_11命令前栈中元素的数目。
输入/输出.
使用codice_13命令，从stdin读取一行并执行它。这允许从宏中向用户要求输入，故而此输入必须是语法上正确的，并且这有潜在的安全问题，因为dc的codice_14命令可以执行任意系统命令。
前面提及过，codice_1命令打印栈顶元素，带有随后的一个换行。codice_16命令弹出栈顶元素并输出它，没有尾随换行。codice_17命令打印整个栈，从栈顶到栈底并且一项一行。
dc还支持控制输入和输出的底数。codice_18命令设置输出底数，输出底数必须大于等于2。codice_19命令弹出栈顶元素并将它用作输入底数，输入底数必须在2和16之间，十六进制数字必须大写以避免和dc命令冲突。要记住输入底数将影响对后面的所有数值的分析，所以通常建议在设置输入底数之前先设置输出底数。例如将二进制转换成十六进制：
$ echo '16o2i 10011010101111001101111011110000 p' | dc
9ABCDEF0
要读取设置的这些数值，codice_20、codice_21和codice_22命令将压入当前精度、输入基数和输出基数到栈顶。
语言特征.
除了上述的基本算术和栈操作，dc包括了对宏、条件和存储结果用于以后检索的支持。
寄存器.
寄存器在dc中是有着单一字符名字的存贮位置，它可以分别通过命令codice_23和codice_24来存储和检索，它是宏和条件的底层机制：codice_25弹出栈顶元素并将它存储入寄存器codice_26，而codice_27将寄存器codice_26的值压入栈顶。例如：
&lt;syntaxhighlight&gt;
3 sc 4 lc * p
&lt;/syntaxhighlight&gt;
寄存器还被当作次要栈，可以使用codice_29和codice_30命令在它们和主要栈之间压入和弹出数值。存储栈顶元素到寄存器中并把这个元素留在栈顶，需要联合使用codice_31命令。
字符串.
字符串是包围在codice_32和codice_33之中的字符，可以被压入栈顶和存入寄存器。使用codice_34命令从栈顶弹出字符串并执行它，使用codice_35命令从栈顶弹出并打印字符串，无尾随换行。codice_36命令可以把数值的低位字节转换成ASCII字符，或者在栈顶是字符串时把它替换为这个字符串的第一个字符。此外没有方法去建造字符串或进行字符串操纵。
codice_37字符开始一个注释直到此行结束。
宏.
通过允许寄存器和栈项目像数值一样存储字符串，从而实现了宏。一个字符串可以被打印，也可以被执行，就是说作为dc命令的序列而传递。例如可以把一个宏“加codice_38并接着乘以codice_39”存储到一个寄存器codice_40中：
&lt;syntaxhighlight&gt;
[1+ 2*]sM
&lt;/syntaxhighlight&gt;
下面的命令将一个数值codice_41压入栈顶，使用codice_34命令执行存储在寄存器codice_40中的宏，并打印留在栈顶的结果：
&lt;syntaxhighlight&gt;
3 lMx p
&lt;/syntaxhighlight&gt;
条件.
最后提供了有条件执行宏的机制。命令codice_44将从栈顶弹出两个值，如果二者相等，则执行存储在寄存器codice_40中的宏。如下命令序列将在原栈顶元素等于5的条件下打印字符串codice_46。
&lt;syntaxhighlight&gt;
[[equal]p]sM d5=M
&lt;/syntaxhighlight&gt;
这里使用了codice_9命令保留原栈顶元素。其他条件有codice_48、codice_49、codice_50、codice_51、codice_52，如果栈顶元素分别大于、不大于（小于等于）、小于、不小于（大于等于）、不等于仅次于栈顶的元素，则执行指定的宏。注意不同于[[Forth]]、[[PostScript#PostScript语言|PostScript]]和[[Factor语言|Factor]]，在不等式比较中的运算元的次序同在算术中的次序相反， 等价于中缀表示法的codice_53codice_54codice_41，然而在codice_41codice_50codice_53时运行寄存器的内容。下面是有条件执行宏的示例：
$ echo 5 | dc -e '? [[equal]p]sM d5=M'
equal
$ echo 4 | dc -e '? [[equal]p]sM d5=M'
这里的命令在栈顶元素等于codice_53之时有相应的输出，在它不等于codice_53之时没有输出。
如果要实现一般编程语言中表达有两个[[分支 (计算机科学)|分支]][[控制流程]]的[[条件运算符]][[条件运算符?:|codice_61]]或[[条件 (编程)|条件语句]]，则需要两个宏构造，例如：
$ echo 5 | dc -e '? [[equal]p q]sM [d5=M [not equal]p]x'
equal
$ echo 4 | dc -e '? [[equal]p q]sM [d5=M [not equal]p]x'
not equal
这里的命令在栈顶元素等于codice_53和不等于codice_53之时都有相应的输出。
codice_2命令退出2层宏，如果宏少于2层则退出dc。codice_65命令从栈顶弹出一个值作为退出宏的层数，比如codice_66命令退出2层宏，它永不导致退出dc。
递归示例.
通过定义进行有条件的[[递归]]调用的宏，可以实现递归过程和[[迭代]]运算。
阶乘.
下面是对栈顶元素计算[[阶乘]]formula_2的[[递归 (计算机科学)|递归]][[子程序|过程]]：
&lt;syntaxhighlight&gt;
&lt;/syntaxhighlight&gt;
本文中的[[伪代码]]尽量采用[[条件运算符]][[条件运算符?:|codice_61]]而少用[[条件语句]]。这里的codice_68是过程定义，过程codice_69消费在栈顶的命名为codice_34的一个值；这里的codice_71是过程调用，在调用过程codice_69之前先将codice_73的结果值压入栈顶；这里的codice_74也是过程调用，在调用过程codice_69之前不需要向栈顶压入一个值。
这个过程在codice_76时有效，它可直接转写为[[互递归]]形式：
&lt;syntaxhighlight&gt;
&lt;/syntaxhighlight&gt;
它可实现为：
&lt;syntaxhighlight&gt;
[d 1- lFx *]sG [d1&lt;G]dsFx
&lt;/syntaxhighlight&gt;
计算阶乘还可以采用这个条件表达式的等价形式：
&lt;syntaxhighlight&gt;
&lt;/syntaxhighlight&gt;
它可实现为：
&lt;syntaxhighlight&gt;
[q]sQ [d1!&lt;Q d 1- lFx *]dsFx
&lt;/syntaxhighlight&gt;
这个递归过程可以进一步采用[[尾调用]]写为：
&lt;syntaxhighlight&gt;
&lt;/syntaxhighlight&gt;
这里的[[伪代码]]中的codice_77是过程定义，过程codice_78消费在栈顶的命名为codice_79的一个值，和紧邻其下的命名为codice_34的另一个值；这里的codice_81是过程调用，在调用过程codice_78之前不需要向栈顶压入一个值。它可实现为：
&lt;syntaxhighlight&gt;
[1- lFx]sG [d d1&lt;G]dsFx [* z1&lt;H]dsHx
&lt;/syntaxhighlight&gt;
这里的运算由两个步骤[[串接式编程语言|串接]]而成，首先将递减的整数压入堆栈形成[[整数集]]formula_3上[[偏序关系]]formula_4的[[区间]]formula_5，然后在这个[[数列]]上进行初始值为formula_6的应用[[乘法]]运算的[[fold (高阶函数)|右侧归约]]，最终得出一个结果值。这里的命令codice_11，将当前的堆栈深度即堆栈中元素数目压入栈顶。还可以进一步增加针对初始的栈顶元素codice_34的codice_85情况的预处理，即执行codice_86来实现codice_87。下面是其执行示例：
$ echo 0 | dc -e '? [d-1+]sAd0!&lt;A [1- lFx]sG [d d1&lt;G]dsFx [* z1&lt;H]dsHx p'
1
$ echo 9 | dc -e '? [d-1+]sAd0!&lt;A [1- lFx]sG [d d1&lt;G]dsFx [* z1&lt;H]dsHx p'
362880
下面的例子采用[[迭代法]]打印出[[阶乘]]formula_7的前codice_16项的[[数列]]：
&lt;syntaxhighlight&gt;
&lt;/syntaxhighlight&gt;
这里codice_89中的codice_34指称初始的栈顶元素。这里的迭代过程codice_91不同于一般的递归过程之处，是在调用自身之时仍采用当前栈顶元素为参数而不向堆栈压入新元素。这里所迭代的是需要[[初始化]]的堆栈中的[[自动变量]]codice_34，它是formula_8，其初始值codice_38是formula_9；codice_19既是迭代的递增[[循环计数器|计数器]]，也是的[[运算元]]，其递增形成[[整数集]]formula_3上[[偏序关系]]formula_11的[[区间]]formula_12，同步地在这个数列上进行输出中间值的初始值为formula_6的应用[[乘法]]运算的[[fold (高阶函数)|左侧归约]]，逐项输出中间结果形成[[数列]]，这里的codice_34所起到的作用也被称为[[累加器]]（accumulator），其含义为“累计”或“累积”而不必然采用加法或乘法。它可实现为：
&lt;syntaxhighlight&gt;
sn 1si 1 [lid1+si *p liln!&lt;F]dsFx
&lt;/syntaxhighlight&gt;
下面是其执行示例： 
$ echo 6 | dc -e '? sn 1si 1 [lid1+si *p liln!&lt;F]dsFx'
1
2
6
24
120
720
可以将它改为计算单个的阶乘：
$ echo 0 | dc -e '? sn 1si 1 [lid1+si * liln!&lt;F]dsFx p'
1
$ echo 9 | dc -e '? sn 1si 1 [lid1+si * liln!&lt;F]dsFx p'
362880
斐波那契数.
下面是对栈顶元素计算[[斐波那契数]]formula_14的[[递归 (计算机科学)|递归]]过程：
&lt;syntaxhighlight&gt;
&lt;/syntaxhighlight&gt;
这个过程在codice_96时有效，它可直接转写为[[互递归]]形式：
&lt;syntaxhighlight&gt;
&lt;/syntaxhighlight&gt;
它可实现为：
&lt;syntaxhighlight&gt;
[d 2- lFx r 1- lFx +]sG [d1&lt;G]dsFx
&lt;/syntaxhighlight&gt;
这里的codice_10命令反转（交换）栈顶元素和仅次于栈顶的元素二者的次序，此外dc还有codice_98命令，其参数为正数时是循环左移一位，参数为负数时是循环右移一位，参数的绝对值是参与循环移位的堆栈中含栈顶的元素数目，比如后文中用到codice_99命令会将堆栈顶部的三个元素codice_100转变为codice_101。
下面是其执行示例，并展示了通过给它加打印[[断点]]来观察其递归调用的规模：
$ echo 0 | dc -e '? [d 2- lFx r 1- lFx +]sG [d1&lt;G]dsFx p'
0
$ echo 20 | dc -e '? [d 2- lFx r 1- lFx +]sG [d1&lt;G]dsFx p'
6765
$ echo 20 | dc -e '? [p d 2- lFx r 1- lFx +]sG [d1&lt;G]dsFx' | wc -l
10945
$ echo 21 | dc -e '? [d 2- lFx r 1- lFx +]sG [d1&lt;G]dsFx p'
10946
这个过程可以进一步采用[[记忆化]]而写为：
&lt;syntaxhighlight&gt;
&lt;/syntaxhighlight&gt;
基于[[数组]]的保存命令codice_102和访问命令codice_103，它可实现为：
&lt;syntaxhighlight&gt;
1si [d 2- lFx r 1- lFx +]sG [;a q]sN [dli!&lt;N lGx dli1+dsi:a]sM [d1&lt;M]dsFx
&lt;/syntaxhighlight&gt;
在[[递归定义]]formula_14中，正在计算并要记忆其结果的是formula_16，即将它的结果存储在这里的codice_104中，其下标为codice_16，而当前已存储过的最大下标是codice_106，将要存储的下标是已经存储的最大下标的增加codice_38。这里的codice_19是数组codice_36的已存储过的最大下标，它的初始值是codice_38，即假定codice_111和codice_112已经存储了数值。这里的codice_91在被遇到codice_4和codice_38这两个参数之时，直接在代码中返回数值，而不会用它们作为下标去访问数组。
这个过程可以更一般化的写为：
&lt;syntaxhighlight&gt;
&lt;/syntaxhighlight&gt;
这里的伪代码中的codice_116表示复制主栈的栈顶元素codice_34并把它压入[[堆栈|栈]]codice_118的栈顶，codice_119表示弹出[[堆栈|栈]]codice_118的栈顶元素并把它压入主栈的栈顶。基于寄存器关联[[堆栈|栈]]的保存命令codice_29和装载命令codice_30，它可实现为：
&lt;syntaxhighlight&gt;
0 0:a 1 1:a 1si [d 2- lFx r 1- lFx +]sG [;a q]sN [dli!&lt;N dSb lGxd Lbdsi:a]dsFx
&lt;/syntaxhighlight&gt;
在[[递归定义]]formula_14的[[递归]]调用中，随着formula_16的索引codice_16的递减，而递归下降至被计算出来并保存的第一项formula_19，然后沿着调用链逐级返回。在递归的每个步骤中都要计算并保存formula_20，它所需要的formula_21和formula_22这两项之中：
下面是其执行示例，展示了通过给它加打印[[断点]]来观察其递归调用的具体步骤，其中连续高亮标示出某个过程从开始被调用直到它结束返回：
$ echo 8 | dc -e '? 0 0:a 1 1:a 1si [d 2- lFx r 1- lFx +]sG [;a q]sN [[Level]nzn[ F]np dli!&lt;N dSb lGxd Lbdsi:a]dsFx'
Level1 F8
Level2 F6
Level3 F4
Level4 F2
Level5 F0
Level5 F1
Level4 F3
Level5 F1
Level5 F2
Level3 F5
Level4 F3
Level4 F4
Level2 F7
Level3 F5
Level3 F6
$ echo 8 | dc -e '? 0 0:a 1 1:a 1si [d 1- lFx r 2- lFx +]sG [;a q]sN [[Level]nzn[ F]np dli!&lt;N dSb lGxd Lbdsi:a]dsFx'
Level1 F8
Level2 F7
Level3 F6
Level4 F5
Level5 F4
Level6 F3
Level7 F2
Level8 F1
Level8 F0
Level7 F1
Level6 F2
Level5 F3
Level4 F4
Level3 F5
Level2 F6
递归过程的返回值之间形成了[[递推关系]]，每个formula_16的值被用到一次或两次，首次是从递归调用直接返回，再次是对其已保存的值进行访问，每个步骤要么访问两项并保存两项，要么访问一项并保存一项。故而它可以进一步写为：
&lt;syntaxhighlight&gt;
&lt;/syntaxhighlight&gt;
它可实现为：
&lt;syntaxhighlight&gt;
1si [d 2- lFx r 1- lFx +]sG [2%;a q]sN [dli!&lt;N lGx dli1+dsi2%:a]sM [d1&lt;M]dsFx
&lt;/syntaxhighlight&gt;
在[[递归定义]]formula_14的上述两种求值次序中，首先进行formula_21递归调用的这种形式，其[[记忆化]]实现从一开始就持续进行递归调用直到首次递归返回，然后就逐级递归返回而不再进行递归调用，故而它可以进一步写为：
&lt;syntaxhighlight&gt;
&lt;/syntaxhighlight&gt;
这里的codice_36被[[初始化]]为formula_32的值codice_4，codice_34在步入递归调用之后，除了在被递减至formula_33的值codice_38之时作为调用结果来返回之外，没有参与到后续的[[加法]]运算之中，而主要起到了递减[[循环计数器|计数器]]的作用。它可实现为：
&lt;syntaxhighlight&gt;
0sa [1- lFx la r dsa +]sG [d1&lt;G]dsFx
&lt;/syntaxhighlight&gt;
下面的例子采用[[迭代法]]打印出[[斐波那契数列]]的不含第codice_4项的前codice_16项：
&lt;syntaxhighlight&gt;
&lt;/syntaxhighlight&gt;
这里codice_130中的codice_34指称初始的栈顶元素。这里的迭代过程codice_91不同于一般的递归过程之处，是在调用自身之时仍采用当前栈顶元素为参数而不向堆栈压入新元素。针对[[递推关系]]formula_34，它总共进行codice_16次迭代运算得出formula_33到formula_16。这里的codice_19是进行迭代的递减[[循环计数器|计数器]]，所迭代的是需要[[初始化]]的两个变量codice_34和codice_36：在堆栈中的[[自动变量]]codice_34，先是被迭代的formula_21，再是迭代出来的formula_20，其初始值codice_4是formula_39，它所起到的作用也被称为[[累加器]]；作为[[全局变量]]的codice_36，以formula_40所得的codice_38作为初始值，在首次迭代之后它是始于formula_32的formula_22。它可实现为：
&lt;syntaxhighlight&gt;
si 1sa [lardsa +p li1-si li0&lt;F]sF 0 li0&lt;F
&lt;/syntaxhighlight&gt;
下面是其执行示例： 
$ echo 6 | dc -e '? si 1sa [lardsa +p li1-si li0&lt;F]sF 0 li0&lt;F'
1
1
2
3
5
8
可以将它改为计算单个的斐波那契数：
$ echo 0 | dc -e '? si 1sa [lardsa + li1-si li0&lt;F]sF 0 li0&lt;F p'
0
$ echo 9 | dc -e '? si 1sa [lardsa + li1-si li0&lt;F]sF 0 li0&lt;F p'
34
男人抑或男孩测试.
下面的例子通过[[编译器递归测试|男人抑或男孩测试]]展示dc的数组能支持递归运算的程度，这个测试用[[Python]]可以写为：
import sys
sys.setrecursionlimit(2**10 + 9) # 这里设置的递归限制在实测时刚好够用于k=10
def A(k, x1, x2, x3, x4, x5):
 def B():
 nonlocal k
 k -= 1
 return A(k, B, x1, x2, x3, x4)
 return x4() + x5() if k &lt;= 0 else B()
print(A(10, lambda: 1, lambda: -1, lambda: -1, lambda: 1, lambda: 0))
通过手工设立并操纵简易[[调用栈]]、和[[头等函数]]，这个测试可以用dc实现为：
&lt;syntaxhighlight&gt;
0 0:M 0sS 0sT 0 0:K
[lT1+sS z d1+lT+sT dlRxsM]sP 
[d_3RlS+:M 1- d0!&gt;R]sR
[lTd;M-1-sT lTd;M-sS]sQ
[lI1+sI lI:W lSlI:K]sU
[lI1-sI]sV [d;Wx]sW
[lPx lFx lQx]sA [lS;M0!&lt;G [lBx]lUx lIlBx lVx]sF
[lS5+;M lWx SO lS4+;M lWx LO + q]sG
[d;KsL sJ lL;M1-lL:M lL;M lJ lL1+;M lL2+;M lL3+;M lL4+;M lAx]sB
4sI [sM1]0:W [sM_1]1:W [sM_1]2:W [sM1]3:W [sM0]4:W
? 0 1 2 3 4 lAx p
&lt;/syntaxhighlight&gt;
对于codice_141，这个测试中函数codice_142执行了codice_143次，这里它在[[求值策略#传值调用|传值调用]]形式参数小于等于零的情况下，先求值第codice_53个[[求值策略#传名调用|传名调用]]形式参数再求值第codice_145个传名调用形式参数，其在调用栈中的[[栈帧]]数量的最大值即递归深度是codice_146层；而为函数codice_147的创建了codice_148个[[闭包 (计算机科学)|闭包]]，其堆叠数量的最大值是codice_149层；并为[[常数函数|常量函数]]创建了codice_53个闭包。
将上述代码保存入codice_151文件中，下面是针对codice_3为codice_4到codice_154它的执行示例： 
$ for i in $(seq 0 10); do echo $i | dc manorboy.dc; done
1
0
-2
0
1
0
1
-1
-10
-30
-67
可以将[[闭包 (计算机科学)|闭包]]也存储在调用栈之中：
&lt;syntaxhighlight&gt;
0 0:M 0sS 0sT 0 0:K 0 0:N 0sI
[lT1+sS z d1+lT+sT dlRxsM lI1+sI lSlI:K lTlI:N]sP 
[d_3RlS+:M 1- d0!&gt;R]sR
[lTd;M-1-sT lTd;M-sS lI1-sI]sQ
[d;N1-;Mx]sW
[[lBx]lPx lFx lQx]sA [lS;M0!&lt;G lIlBx]sF
[lS5+;M lWx SO lS4+;M lWx LO + q]sG
[d;KsL sJ lL;M1-lL:M lL;M lJ lL1+;M lL2+;M lL3+;M lL4+;M lAx]sB
[sM1]lPx [sM_1]lPx [sM_1]lPx [sM1]lPx [sM0]lPx
? 1 2 3 4 5 lAx p
&lt;/syntaxhighlight&gt;
这里在codice_141的情况下，调用栈的递归深度为codice_156层，如果在函数codice_142中先求值第codice_145个传名调用形式参数再求值第codice_53个传名调用形式参数，则递归深度为codice_160层。
外部链接.
[[Category:面向堆栈编程语言]]
[[Category:跨平台软件]]
[[Category:Unix软件]]
[[Category:软件计算器]]
[[Category:自由数学软件]]
[[Category:数值分析语言]]