Dc (程序)

dc（desk calculator：桌面计算器）是采用逆波兰表示法的跨平台计算器，它支持任意精度算术。它是在贝尔实验室工作期间书写的，作为最老的Unix实用工具，先于C语言的发明。像那个年代的其他实用工具一样，它有着一组强力的特征和简洁的语法。传统上，采用中缀表示法的bc计算器程序是在dc之上实现的。
历史.
dc是幸存的最老的Unix语言。在贝尔实验室收到第一台PDP-11的时候，用B语言写成的dc是在这个新机器上运行的第一个语言，甚至在汇编器之前。
基本运算.
在dc中要做4和5的乘法：
<syntaxhighlight lang="console" highlight="4">
$ dc
4 5 *
p
20
q
</syntaxhighlight>
这可转译为“把4和5压入栈顶，通过乘法算符，从栈中弹出两个元素，将二者相乘并把结果压回栈顶”。接着使用codice_1命令打印栈顶的元素。使用codice_2命令退出此次调用的dc实例。注意数值相互间必须以空白分隔，但某些算符可以不必如此。
还可以用如下命令得到这个结果：
<syntaxhighlight lang="console" highlight="7">
$ 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
</syntaxhighlight>
使用命令codice_3来变更算术精度，它设置算术运算的小数位数。因为缺省精度是codice_4，例如：
<syntaxhighlight lang="console">
$ dc -e '10946 6765 / p'
1
</syntaxhighlight>
通过使用命令codice_3调整精度，可以产生任意数目的小数数位，例如：
<syntaxhighlight lang="console">
$ dc -e '7k 10946 6765 / p'
1.6180339
</syntaxhighlight>
dc有科学计算器的基本运算功能，比如求黄金分割率formula_1的值：
<syntaxhighlight lang="console">
$ dc -e '7k 5 v 1 + 2 / p'
1.6180339
</syntaxhighlight>
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命令冲突。要记住输入底数将影响对后面的所有数值的分析，所以通常建议在设置输入底数之前先设置输出底数。例如将二进制转换成十六进制：
<syntaxhighlight lang="console">
$ echo '16o2i 1001101010111100110111101111 p' | dc
9ABCDEF
</syntaxhighlight>
要读取设置的这些数值，codice_20、codice_21和codice_22命令将压入当前精度、输入基数和输出基数到栈顶。
语言特征.
除了上述的基本算术和栈操作，dc包括了对宏、条件和存储结果用于以后检索的支持。
寄存器.
寄存器在dc中是有着单一字符名字的存贮位置，它可以通过命令来存储和检索，它是宏和条件的底层机制：codice_23弹出栈顶元素并将它存储入寄存器codice_24，而codice_25将寄存器codice_24的值压入栈顶。例如：
<syntaxhighlight>
3 sc 4 lc * p
</syntaxhighlight>
寄存器还被当作次要栈，可以使用codice_27和codice_28命令在它们和主要栈之间压入和弹出数值。存储栈顶元素到寄存器中并把这个元素留在栈顶，需要联合使用codice_29命令。
字符串.
字符串是包围在codice_30和codice_31之中的字符，可以被压入栈顶和存入寄存器。使用codice_32命令从栈顶弹出字符串并执行它，使用codice_33命令从栈顶弹出并打印字符串，无尾随换行。codice_34命令可以把数值的低位字节转换成ASCII字符，或者在栈顶是字符串时把它替换为这个字符串的第一个字符。此外没有方法去建造字符串或进行字符串操纵。
codice_35字符开始一个注释直到此行结束。
宏.
通过允许寄存器和栈项目像数值一样存储字符串，从而实现了宏。一个字符串可以被打印，也可以被执行，就是说作为dc命令的序列而传递。例如可以把一个宏“加codice_36并接着乘以codice_37”存储到一个寄存器codice_38中：
<syntaxhighlight>
+ 2 *sm
</syntaxhighlight>
通过使用codice_32命令弹出栈顶的字符串并执行之，如下这样使用存储的宏：
<syntaxhighlight>
3 lmx p
</syntaxhighlight>
codice_40命令从栈顶弹出一个值作为退出宏的层数，比如codice_41命令退出2层宏，它永不导致退出dc。codice_2命令退出2层宏，如果宏少于2层则退出dc。
条件.
最后提供了有条件执行宏的机制。命令codice_43将从栈顶弹出两个值，如果二者相等，则执行存储在寄存器codice_10中的宏。如下命令序列将在原栈顶元素等于5的条件下打印字符串codice_45。
<syntaxhighlight>
[p]sm d5=m
</syntaxhighlight>
这里使用了codice_9命令保留原栈顶元素。其他条件有codice_47、codice_48、codice_49、codice_50、codice_51，如果栈顶元素分别大于、不大于（小于等于）、小于、不小于（大于等于）、不等于仅次于栈顶的元素，则执行指定的宏。注意不同于Forth、PostScript和Factor，在不等式比较中的运算元的次序同在算术中的次序相反， 等价于中缀表示法的，然而在时运行寄存器的内容。下面是其执行示例：
<syntaxhighlight lang="console">
$ echo 5 | dc -e '? [p]sm d5=m'
equal
$ echo 5 | dc -e '? [[d5=m [not equalp]x'
equal
$ echo 4 | dc -e '? [[d5=m [not equalp]x'
not equal
</syntaxhighlight>
迭代示例.
通过定义进行有条件的递归调用的宏，可以实行迭代运算。
阶乘.
下面是对栈顶元素计算阶乘formula_2的递归过程：
<syntaxhighlight>
</syntaxhighlight>
它在codice_52时有效，它可采用互递归写为：
<syntaxhighlight>
</syntaxhighlight>
这里的伪代码采用了条件运算符而非条件语句。它可实现为：
<syntaxhighlight>
1- lFx *sG [d1<G]dsFx
</syntaxhighlight>
它可以进一步采用尾递归实现为：
<syntaxhighlight>
1- lFxsG 1 [* z1<HdsHx
</syntaxhighlight>
这里的运算由两个步骤串接而成，首先将递减的整数压入堆栈形成整数集formula_3上偏序关系formula_4的区间formula_5，然后在这个数列上进行应用乘法运算的右归约得出一个最终的结果值。这里的命令codice_11，将当前的堆栈深度即堆栈中元素数目压入栈顶。还可以进一步增加针对codice_54情况的预处理。下面是其执行示例：
<syntaxhighlight lang="console">
$ echo 0 | dc -e '? [d 1- lFxsG 1 [* z1<HdsHx p'
1
$ echo 9 | dc -e '? [d 1- lFxsG 1 [* z1<HdsHx p'
362880
</syntaxhighlight>
下面的例子采用迭代法打印出阶乘formula_6的前codice_16项的数列：
<syntaxhighlight>
</syntaxhighlight>
这里迭代的是需要初始化的堆栈中的自动变量codice_32，它是formula_7，其初始值codice_36是formula_8；codice_19既是迭代的递增计数器，也是的运算元，其递增形成整数集formula_3上偏序关系formula_10的区间formula_11，同步地在这个数列上进行输出中间值的应用乘法运算的左归约得出结果数列，这里的codice_32所起到的作用也被称为累加器，其含义为“累计”或“累积”而不必然采用加法或乘法。它可实现为：
<syntaxhighlight>
sn 0si 1 *p liln>FdsFx
</syntaxhighlight>
下面是其执行示例： 
<syntaxhighlight lang="console">
$ echo 6 | dc -e '? sn 0si 1 *p liln>FdsFx'
1
2
6
24
120
720
</syntaxhighlight>
可以将它改为计算单个的阶乘：
<syntaxhighlight lang="console">
$ echo 0 | dc -e '? sn 0si 1 * liln>FdsFx p'
1
$ echo 9 | dc -e '? sn 0si 1 * liln>FdsFx p'
362880
</syntaxhighlight>
斐波那契数.
下面是对栈顶元素计算斐波那契数的递归过程：
<syntaxhighlight>
</syntaxhighlight>
它在codice_60时有效，它可采用互递归写为：
<syntaxhighlight>
</syntaxhighlight>
它可实现为：
<syntaxhighlight>
2- lFx r 1- lFx +sG [d1<G]dsFx
</syntaxhighlight>
这里的codice_10命令反转（交换）栈顶元素和仅次于栈顶的元素二者的次序。它可以进一步采用记忆化而写为：
<syntaxhighlight>
</syntaxhighlight>
基于数组保存命令codice_62和访问命令codice_63，它可实现为：
<syntaxhighlight>
0Sa 1si 2- lFx r 1- lFx +sG [;aq]sN lGx dli1+dsi:asM [d1<M]dsFx Lasa
</syntaxhighlight>
或者更一般化的写为：
<syntaxhighlight>
</syntaxhighlight>
它可实现为：
<syntaxhighlight>
0Sa 0 0:a 1 1:a 1si 2- lFx r 1- lFx +sG [;aq]sN lGx dli1+dsi:adsFx Lasa
</syntaxhighlight>
下面是其执行示例，展示了通过给它加打印断点来观察其递归调用的规模和详细步骤：
<syntaxhighlight lang="console">
$ echo 0 | dc -e '? 2- lFx r 1- lFx +sG [d1<G]dsFx p'
0
$ echo 20 | dc -e '? 2- lFx r 1- lFx +sG [d1<G]dsFx p'
6765
$ echo 21 | dc -e '? 2- lFx r 1- lFx +sG [d1<G]dsFx p'
10946
$ echo 20 | dc -e '? d 2- lFx r 1- lFx +sG [d1<G]dsFx' | wc -l
10945
$ echo 20 | dc -e '? 0Sa 0 0:a 1 1:a 1si 2- lFx r 1- lFx +sG [;aq]sN dli!<N lGx dli1+dsi:adsFx Lasa' | wc -l
39
$ echo 6 | dc -e '? 0Sa 1si 2- lFx r 1- lFx +sG [;aq]sN Fnp dli!<N lGx dli1+dsi:a]sM [d1<M]dsFx Lasa'
1 F6
2 F4
3 F2
3 F3
4 F2
2 F5
3 F3
3 F4
$ echo 6 | dc -e '? 0Sa 1si 1- lFx r 2- lFx +sG [;aq]sN Fnp dli!<N lGx dli1+dsi:a]sM [d1<M]dsFx Lasa'
1 F6
2 F5
3 F4
4 F3
5 F2
4 F2
3 F3
2 F4
</syntaxhighlight>
在递归定义formula_12的递归调用中，随着formula_13的索引codice_16的递减，而递归下降至被计算出来并保存的第一项formula_14，然后沿着调用链逐级返回；在递归的每个步骤中都要计算并保存formula_15，它所需要的formula_16和formula_17这两项之中，如果首先进行formula_17递归调用并从其返回，则formula_16也需要计算并保存，为此需要访问已保存的formula_17和formula_21；如果首先进行formula_16递归调用并从其返回，则需要访问已保存的formula_17；这种递归返回形成了递推关系，每个formula_13的值被用到一次或两次，首次是从递归调用直接返回，再次是对其已保存的值进行访问，每个步骤要么访问两项并保存两项，要么访问一项并保存一项。故而它可以进一步写为：
<syntaxhighlight>
1si 2- lFx r 1- lFx +sG [dli!<N lGx dli1+dsi2%:asM [d1<M]dsFx
</syntaxhighlight>
在递归定义formula_12的上述两种求值次序中，首先进行formula_16递归调用的这种形式，其记忆化实现从一开始就持续进行递归调用直到首次递归返回，然后就逐级递归返回而不再进行递归调用，故而它可以进一步写为：
<syntaxhighlight>
</syntaxhighlight>
这里的codice_34被初始化为formula_27的值codice_4，codice_32在步入递归调用之后，除了在被递减至formula_28的值codice_36之时作为调用结果来返回之外，没有参与到后续的加法运算之中，而主要起到了递减计数器的作用。它可实现为：
<syntaxhighlight>
0sa lFx la r dsa +sG [d1<G]dsFx
</syntaxhighlight>
下面的例子采用迭代法打印出斐波那契数列的不含第codice_4项的前codice_16项：
<syntaxhighlight>
</syntaxhighlight>
针对递推关系formula_29，它总共进行codice_16次迭代运算得出formula_28到formula_13。这里的codice_19是进行迭代的递减计数器，所迭代的是需要初始化的两个变量codice_32和codice_34：在堆栈中的自动变量codice_32，先是被迭代的formula_16，再是迭代出来的formula_15，其初始值codice_4是formula_34，它所起到的作用也被称为累加器；作为全局变量的codice_34，以formula_35所得的codice_36作为初始值，在首次迭代之后它是始于formula_27的formula_17。它可实现为：
<syntaxhighlight>
si 1sa 0 +p li1-si lFxsG [li0<G]dsFx
</syntaxhighlight>
下面是其执行示例： 
<syntaxhighlight lang="console">
$ echo 6 | dc -e '? si 1sa 0 +p li1-si lFxsG [li0<G]dsFx'
1
1
2
3
5
8
</syntaxhighlight>
可以将它改为计算单个的斐波那契数：
<syntaxhighlight lang="console">
$ echo 0 | dc -e '? si 1sa 0 + li1-si lFxsG [li0<G]dsFx p'
0
$ echo 9 | dc -e '? si 1sa 0 + li1-si lFxsG [li0<G]dsFx p'
34
</syntaxhighlight>