aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastiano Tronto <sebastiano@tronto.net>2023-03-30 12:22:35 +0200
committerSebastiano Tronto <sebastiano@tronto.net>2023-03-30 12:22:35 +0200
commit2fb07a9a7d477253fb5ba4d96e0a08d770593d59 (patch)
treefd38f50b8fc94a1e0c5460d043d8cb360b360133
parentf17a33a74beec870ccd7815aaed90ccad0d21612 (diff)
downloadsebastiano.tronto.net-2fb07a9a7d477253fb5ba4d96e0a08d770593d59.tar.gz
sebastiano.tronto.net-2fb07a9a7d477253fb5ba4d96e0a08d770593d59.zip
Added blog post
-rw-r--r--src/blog/2023-03-30-dc/dc.md320
-rw-r--r--src/blog/blog.md1
-rw-r--r--src/blog/feed.xml7
3 files changed, 328 insertions, 0 deletions
diff --git a/src/blog/2023-03-30-dc/dc.md b/src/blog/2023-03-30-dc/dc.md
new file mode 100644
index 0000000..75b27c4
--- /dev/null
+++ b/src/blog/2023-03-30-dc/dc.md
@@ -0,0 +1,320 @@
1# The man page reading club: dc(1)
2
3For this episode I have decided to go back to the basics, in multiple
4ways. Indeed `dc`, the *desk calculator*, is:
5
6* A calculator, the most basic functionality for a computer to be called
7 so - "computer" *literaly* means "calculator".
8* A [stack-machine](https://en.wikipedia.org/wiki/Stack_machine), one of
9 the most basic
10 [Turing-complete](https://en.wikipedia.org/wiki/Turing-complete)
11 computational models.
12* One of the oldest UNIX utilities, predating even the C language - in fact,
13 it was originally written in
14 [B](https://en.wikipedia.org/wiki/B_programming_language).
15
16But is it also a practical tool to use? Let's find out!
17
18## dc(1)
19
20*Follow along at [man.openbsd.org](http://man.openbsd.org/OpenBSD-7.2/dc)*
21
22There are a few features marked as non-portable in the manual page, most
23of them relevant to OpenBSD's [bc](http://man.openbsd.org/OpenBSD-7.2/bc)
24implementation. To make the post a bit shorter, I have decided to skip
25all of them.
26
27The first few lines of the manual page explain that `dc` uses
28[reverse Polish notation](https://en.wikipedia.org/wiki/Reverse_polish_notation):
29numbers can be pushed onto a stack, and operations are performed on the top
30(or top two) numbers on the stack, their result being pushed back onto the
31stack to replace the operands.
32
33`dc` allows to set an arbitrary precision (here called *scale*), as well
34as different bases for input and output - for example, you may want to
35input your numbers in binary and read the output in hexadecimal. The
36output base can be any number greater than 1, but the input base must
37be between 2 and 16.
38
39The most basic operation you can perform is simply pushing a number
40onto the stack. Letters A to F can be used to input numbers in bases
41higher than 10, and negative numbers are written with an underscore `_`
42instead of dash `-`.
43
44The commands are listed in alphabetic order in the manual page, but I will
45instead separate them in more logical sections.
46
47### Basic operations
48
49The most basic operations are `+` (sum), `-` (subtraction), `*`
50(multiplication), `/` (division), `%` (remainder or modulus) and
51`^` (exponentiation). There is also `v` (square root).
52
53For example the command `4 7-` results in `-3`. You can input it
54like that, all on one line and without any whitespace between the
55`7` and the `-`. But if you do, you won't get any output. Why?
56
57### Stack manipulation
58
59Operations remove one or more numbers from the stack and push back the
60result. So the answer to the previous question is: the result was pushed
61onto the stack, but no instruction was given to print it.
62
63This can be done with the command `p`, which prints the top number in
64the stack. The command `f` prints the whole stack. Both of them leave
65the stack unchanged. So for example:
66
67```
68$ echo '4 7-p' | dc
69-3
70```
71
72*(Notice how we redirected the output of `echo` to be read by `dc` -
73I'll never get tired of
74[this](https://en.wikipedia.org/wiki/Pipeline_(Unix)))*
75
76Commands that manipulate the stack are `c` to clear the whole stack
77and `d` to duplicate the top element. The command `z` pushes onto the
78stack the number of elements currently on the stack.
79
80### Scale and bases
81
82As mentioned at the beginning, some global parameters can be set:
83input base, output base and scale. This can be done with the commands
84`i`, `o`, and `k`, respectively: each of them pops the top element
85of the stack and uses it as value to set the respective global parameter.
86
87The capitalized version of these commands, `I`, `O` and `K`, read
88the value of the input base, the output base or scale respectively
89and push it onto the stack
90
91Each number on the stack has its own scale, too. This value is derived
92from the global scale and the scales of the operands used to compute it.
93More precisely:
94
95```
96For addition and subtraction, the scale of the result is the maximum
97of scales of the operands. For division the scale of the result
98is defined by the scale set by the k operation. For multiplication,
99the scale is defined by the expression min(a+b,max(a,b,scale)),
100where a and b are the scales of the operands, and scale is the scale
101defined by the k operation. For exponentiation with a non-negative
102exponent, the scale of the result is min(a*b,max(scale,a)), where
103a is the scale of the base, and b is the value of the exponent. If
104the exponent is negative, the scale of the result is the scale
105defined by the k operation.
106```
107
108The command `X` can be used to replace the top number with its
109scale. Similarly, the command `Z` replaces the top number with its
110length, i.e. its number of digits (not counting eventual decimal point
111or negative sign).
112
113### Registers and arrays
114
115So far we have seen that `dc` can do everything that a rather basic
116RPN calculator can do. Things are going to get
117much more interesting in the next two sections.
118
119`dc` allows the use of 256 *registers* to store data. Each register
120is labelled by a single byte - in practice, an ASCII character.
121This character can be anything, even a whitespace or a non-printable
122character, so make sure not to put unneeded whitespace before a
123register name.
124
125The actual structure of registers was not very clear to me from the
126manual page. I had to read the relevant section and command
127descriptions a few times, and in the end I resorted to the ultimate
128technique: try it out. (I skipped the "read the source code" step,
129please forgive my impurity.)
130
131It turns out that each register is a stack, each level of which
132contains both a single number and an unbounded array of numbers.
133The single number and the array can be manipulated separately. All
134the values default to 0 if unset.
135
136The command `sr` can be used to pop the top element of stack and
137save it as the "single" value of register `r`. You can replace `r`
138by any other ASCII character to manipulate other registers. To load
139the "single" value from register `r` onto the main stack, you can
140use `lr`; this command does not alter the state of the register.
141
142To manipulate a register's array, you can use `;r` and `:r`:
143
144```
145:r Pop two values from the stack. The second value on the stack is
146 stored into the array r indexed by the top of stack.
147
148;r Pop a value from the stack. The value is used as an index into
149 register r. The value in this register is pushed onto the stack.
150```
151
152So for example `42 3:r` stores the number 42 in the third position of
153the array of register `r`, and `3;r` retrieves this value.
154
155So far so good. But I said that each register is actually a stack. What
156did I mean by that?
157
158The commands `Sr` and `Lr` (capital S and L) can be used for this: `Sr`
159creates a new stack level on register `r`, pops the top value of the
160main stack, and saves that value as the "single" value. In doing so,
161a new level of the register's array is also created. Conversely, `Lr`
162pops a level of register `r` and pushes its single value onto the main
163stack, deleting the whole array saved on the level that was popped.
164
165Let's work out an example to help us understand this. First, we push
166some numbers on register `a`:
167
168```
1691sa
170100 0:a 101 1:a 102 2:a
171```
172
173Now register `a` looks something like this:
174
175```
176Level 1 --- single value: 1 --- array: 100 101 102 0 0 ...
177```
178
179You can confirm this by running the commands `la 0;a 1;a 2;a f`,
180which should output the numbers 102, 101, 100 and 1, one per line.
181
182Now let's push another level onto the register with `2Sa`. The
183register now looks something like this:
184
185```
186Level 2 --- single value: 2 --- array: 0 0 0 0 0 ...
187Level 1 --- single value: 1 --- array: 100 101 102 0 0 ...
188```
189
190Running the same command as before (`la 0;a 1;a 2;a f`) should
191now yield 0, 0, 0, 2.
192
193Lastly, let's pop the top level of this register with `La`. Now
194it should look like this again:
195
196```
197Level 1 --- single value: 1 --- array: 100 101 102 0 0 ...
198```
199
200And you can check this with the usual command. If you do, you'll
201notice that the number `2` has also been pushed on the main stack
202by the `La` command.
203
204Phew, this was a long one! And we have not reached the most
205interesting part yet...
206
207### Strings and macros
208
209In `dc` you can work not only with numbers, but also with strings.
210You can input a string by enclosing it in square brackets, like
211this: `[Hello, World!]`. Square brackets can appear in a string
212if they are either balanced or escaped by a backslash.
213
214Strings can be pushed onto the main stack or saved in any register
215like numbers. But what can you do with them? One thing you can do
216is print them with the `P` command:
217
218```
219[Hello, World!
220]P
221Hello, World!
222```
223
224As you can see, it is very easy to include a newline in a string.
225
226But much more interesting is the fact that you can *execute* strings
227with the `x` command. This allows you to create macros. For example,
228say you want to evaluate the function `p(x)=x^2+2x-1`. Since we are
229working in RPN, it is probably easier to rewrite `p(x)` as
230`x(x+2)-1`. If your number `x` is on the stack, you can compute `p(x)`
231with the commands `d2+*1-`. But what if you want to do this
232multiple times? Here macros can help:
233
234```
235[d2+*1-]sp
236```
237
238Now we have saved the macro "evaluate p(x)" on the register `p`. We
239can execute it any time we want by loading it with `lp` and then
240executing it with `x`:
241
242```
2433 lpx
244_2 lpx
2451 lpx
246f
247```
248
249Should give 2, -1, 14.
250
251### Conditionals
252
253Lastly, we can control the flow of macro execution using conditionals:
254
255```
256<x >x =x !<x !>x !=x
257 The top two elements of the stack are popped and compared.
258 Register x is executed if they obey the stated relation.
259```
260
261Let's see a simple example: computing the average of all numbers
262on the stack.
263
264First we need to save the number of elements somewhere, say in the
265register `n`. We can do this with `zsn`. Then we need to sum the
266whole stack. We can do this by calling `+` until the stack is only
267one element left... this sounds like a loop, but we can use recursion
268instead:
269
270```
271[+z1<a]sa
272```
273
274This saves the the macro `[+z1<a]` in register `a`, achieving recursion:
275the macro starts by summing the top two numbers, then pushes the number
276of elements left onto the stack with `z`, followed by one. It then pops
277these two numbers and calls itself if the top one is less then the second.
278
279Putting this all together, we can compute the average of a bunch of
280numbers, say to two decimal digits, like this:
281
282```
28310 12 11 9 8 10 11 10 10
2842k
285[+z1<a]sa
286zsnlaxln/p
287```
288
289Not the most legible code, but quite short!
290
291## Conclusion
292
293In the end I managed to write a rather lengthy post about something as
294simple as a desk calculator. And I have even skipped some things, like
295recursion levels and the `?` command!
296
297Initially I wanted to write about
298[bc(1)](http://man.openbsd.org/OpenBSD-7.2/bc), the other standard UNIX
299calculator. It works with the more familiar infix notation and has
300for loops, if / else statements and functions. I even wrote a
301[small library of mathematical functions](https://git.tronto.net/bclibrary)
302to show off! But in the end I thought it would be boring, so I decided
303to learn and write about `dc` instead. In practice I am likely going to
304use bc and my hand-written math library for most purposes - except
305maybe computing averages, that was one example where the terseness of
306`dc` can come in handy.
307
308Fun fact (from the bc manual page):
309
310```
311bc is actually a preprocessor for dc(1), which it invokes automatically,
312unless the -c (compile only) option is present. In this case the
313generated dc(1) instructions are sent to the standard output, instead
314of being interpreted by a running dc(1) process.
315```
316
317I think it would be a fun excercise to try and re-implement `dc`, and
318then bc as a compiler to `dc` code. I could learn a few things about
319compilers with this project! But for now I'll have to put it in the
320ever-growing list of "one day, maybe" ideas.
diff --git a/src/blog/blog.md b/src/blog/blog.md
index c74c703..8589429 100644
--- a/src/blog/blog.md
+++ b/src/blog/blog.md
@@ -5,6 +5,7 @@
5 5
6## 2023 6## 2023
7 7
8* 2023-03-30 [The man page reading club: dc(1)](2023-03-30-dc)
8* 2023-03-06 [Resizing my website's pictures with ImageMagick and find(1)](2023-03-06-resize-pictures) 9* 2023-03-06 [Resizing my website's pictures with ImageMagick and find(1)](2023-03-06-resize-pictures)
9* 2023-02-25 [Job control: one shell is all you need](2023-02-25-job-control) 10* 2023-02-25 [Job control: one shell is all you need](2023-02-25-job-control)
10* 2023-01-28 [The year of the Windows desktop](2023-01-28-windows-desktop) 11* 2023-01-28 [The year of the Windows desktop](2023-01-28-windows-desktop)
diff --git a/src/blog/feed.xml b/src/blog/feed.xml
index ab75982..be949e6 100644
--- a/src/blog/feed.xml
+++ b/src/blog/feed.xml
@@ -9,6 +9,13 @@ Thoughts about software, computers and whatever I feel like sharing
9</description> 9</description>
10 10
11<item> 11<item>
12<title>The man page reading club: dc(1)</title>
13<link>https://sebastiano.tronto.net/blog/2023-03-30-dc</link>
14<description>The man page reading club: dc(1)</description>
15<pubDate>2023-03-30</pubDate>
16</item>
17
18<item>
12<title>Resizing my website's pictures with ImageMagick and find(1)</title> 19<title>Resizing my website's pictures with ImageMagick and find(1)</title>
13<link>https://sebastiano.tronto.net/blog/2023-03-06-resize-pictures</link> 20<link>https://sebastiano.tronto.net/blog/2023-03-06-resize-pictures</link>
14<description>Resizing my website's pictures with ImageMagick and find(1)</description> 21<description>Resizing my website's pictures with ImageMagick and find(1)</description>

Generated with cgit - Back to sebastiano.tronto.net