Dc (컴퓨터 프로그램)

dc(desk calculator)는 임의 정밀도 연산을 지원하는 크로스 플랫폼 역폴란드 표기법 계산기이다. 벨 연구소의 로린다 체리와 로버트 모리스가 작성하였다. dc는 가장 오래된 유닉스 유틸리티 중 하나로, 심지어 C 프로그래밍 언어의 발명보다도 앞서 등장했다. 그 시대의 다른 유틸리티들처럼 강력한 기능 세트를 갖추고 있지만 구문은 매우 간결하다.
전통적으로 중위 표기법을 사용하는 bc 계산기 프로그램이 dc를 기반으로 구현되었으나, 현대적인 GNU dc 구현체는 bc를 기반으로 한다.

 역사 
dc는 현존하는 가장 오래된 유닉스 언어 프로그램이다. dc가 개발된 벨 연구소에 PDP-11이 도입되었을 때, B로 작성된 dc는 어셈블러보다도 먼저 새 컴퓨터에서 실행된 첫 번째 프로그램이었다. 켄 톰프슨은 dc가 해당 기계에서 작성된 최초의 프로그램이었을 것이라는 의견을 피력한 바 있다.

 기본 연산 
dc에서 4와 5를 곱하는 방법은 다음과 같다 (대부분의 공백 문자는 선택 사항임):

$ cat << EOF > cal.txt
4 5 *
p
EOF

$ dc cal.txt
20
$

다음 명령어로도 결과를 얻을 수 있다:

$ echo "4 5 * p" | dc

또는

$ dc -
4 5*pq
20

$ dc
4 5 *
p
20
q

$ dc -e '4 5 * p'

이 과정은 "4와 5를 스택에 푸시한 다음, 곱셈 연산자로 스택에서 두 요소를 팝하여 곱하고 그 결과를 다시 스택에 푸시하라"는 뜻이다. 그 후 p 명령어를 사용하여 스택의 최상단 요소를 검사(화면에 출력)한다. q 명령어는 실행 중인 dc 인스턴스를 종료한다. 일부 연산자는 띄어쓰기가 필요 없지만 숫자는 서로 띄어 써야 한다는 점에 유의해야 한다.

산술 정밀도는 k 명령어로 변경할 수 있으며, 이는 산술 연산에 사용될 소수점 아래 자릿수를 설정한다. 기본 정밀도는 0이므로, 다음 명령어 시퀀스는 결과로 0을 출력한다:

2 3 / p

k를 통해 정밀도를 조정하면 임의의 소수점 자릿수를 생성할 수 있다. 이 명령어 시퀀스는 .66666을 출력한다.

5 k
2 3 / p

를 계산하려면 다음과 같이 한다 (v는 스택 최상단의 제곱근을 계산하고, _는 음수를 입력하는 데 사용됨):

12 _3 4 ^ + 11 / v 22 -
p

스택의 최상단 두 요소를 교체하려면 r 명령어를 사용한다. 최상단 요소를 복제하려면 d 명령어를 사용한다.

 입력/출력 
표준 입력에서 한 줄을 읽으려면 ? 명령어를 사용한다. 이는 해당 줄을 마치 dc 명령어인 것처럼 평가하므로 구문이 정확해야 하며, ! 명령어를 통해 임의의 명령을 실행할 수 있어 보안 문제가 발생할 수 있다.

앞서 언급했듯이 p는 스택 최상단을 출력하고 그 뒤에 줄바꿈을 붙인다. n은 스택 최상단을 팝하고 줄바꿈 없이 출력한다. f는 전체 스택을 한 줄에 하나씩 출력한다.

dc는 임의의 입력 및 출력 진법을 지원한다. i 명령어는 스택 최상단을 팝하여 입력 진법으로 사용한다. 16진수 숫자는 dc 명령어와의 충돌을 피하기 위해 대문자여야 하며 A-F로 제한된다. o 명령어는 출력 진법에 대해 동일한 작업을 수행하지만, 입력 진법이 이후의 모든 수치 파싱에 영향을 미치므로 보통 출력 진법을 먼저 설정하는 것이 권장된다. 따라서 10o는 출력 진법을 현재의 입력 진법으로 설정하며, 일반적으로 10진수로 설정되지는 않는다. 그럼에도 불구하고 Ao는 입력 진법에 상관없이 출력 진법을 10진수로 재설정한다. 값을 읽기 위해 K, I, O 명령어는 현재의 정밀도, 입력 진법, 출력 진법을 스택 최상단에 푸시한다.

예를 들어, 16진수를 2진수로 변환하려면 다음과 같이 한다:

$ echo 16i2o DEADBEEFp | dc
11011110101011011011111011101111

 언어 기능 

 레지스터 
이러한 기본 산술 및 스택 연산 외에도 dc는 매크로, 조건문 및 나중에 검색하기 위한 결과 저장을 지원한다.

매크로와 조건문의 기초가 되는 메커니즘은 레지스터이다. dc에서 레지스터는 단일 문자 이름을 가진 저장 위치로, 값을 저장하거나 불러올 수 있다. sc는 스택 최상단을 팝하여 레지스터 c에 저장하고, lc는 레지스터 c의 값을 스택으로 푸시한다. 예를 들면 다음과 같다:

3 sc 4 lc * p

레지스터는 보조 스택으로도 취급될 수 있으므로, S와 L 명령어를 사용하여 주 스택과 레지스터 간에 값을 푸시하고 팝할 수 있다.

 문자열 
문자열 값은 [와 ] 문자로 둘러싸이며 스택에 푸시되거나 레지스터에 저장될 수 있다. a 명령어는 수치 값의 하위 바이트를 ASCII 문자로 변환하거나, 스택 최상단이 문자열인 경우 이를 문자열의 첫 번째 문자로 대체한다. 문자열을 작성하거나 문자열 조작을 수행하는 방법은 x 명령어로 실행하거나 P 명령어로 출력하는 것 외에는 없다.

# 문자는 줄 끝까지 주석 처리를 시작한다.

 매크로 
레지스터와 스택 항목에 숫자뿐만 아니라 문자열도 허용함으로써 매크로가 구현된다. 문자열은 출력될 수도 있지만, 실행(즉, dc 명령어의 시퀀스로 처리)될 수도 있다. 예를 들어, 1을 더한 다음 2를 곱하는 매크로를 레지스터 m에 저장할 수 있다:

[1 + 2 *] sm

그 다음 (스택 최상단을 실행하는 x 명령어를 사용하여) 다음과 같이 사용할 수 있다:

3 lm x p

 조건문 
마지막으로, 이 매크로 메커니즘을 사용하여 조건문을 제공할 수 있다. =r 명령어는 스택에서 두 값을 팝하고, 두 값이 같을 때만 레지스터 r에 저장된 매크로를 실행한다. 따라서 다음은 스택 최상단 두 값이 같을 때만 equal 문자열을 출력한다:

[[equal]p] sr 5 5 =r

다른 조건문으로는 >, !>, <, !<, !=가 있으며, 각각 스택 최상단 두 값이 더 크거나, 작거나 같을 때("크지 않을 때"), 작거나, 크거나 같을 때("작지 않을 때"), 그리고 같지 않을 때 지정된 매크로를 실행한다. 부등호 비교에서 피연산자의 순서는 산술 연산 순서와 반대라는 점에 유의해야 한다. 는 로 평가되지만, 는 이므로 레지스터의 내용을 실행한다.

 루프 
루프는 (조건부로) 자기 자신을 다시 호출하는 매크로를 정의함으로써 가능하다. 스택 최상단 값의 간단한 계승(factorial)은 다음과 같이 구현할 수 있다:

# F(x): return x!
# if x-1 > 1
# return x * F(x-1)
# otherwise
# return x
[d1-d1
는 비교를 수행하면서 스택에서 두 값을 팝한다.

dc -e "1 2 4 8 16 100 0d[+z1
.]ds.xd1<2'
이 프로그램 역시 Michel Charpentier가 작성하였다.

더 짧은 버전은 다음과 같다.
dc -e "[n=]P?[lfp/dlf%0=Fdvsr]sF[dsf]sJdvsr2sf[dlf%0=Flfdd2%+1+sflr
M]sN[dlf%0=Flfd2+sflr>N]dsMx[p]sMd1
M]sN[dlf%l0=Flfdl2+sflr>N]dsMx[p]sMd1