Introduction
After more than a year of working on an implementation of dc
and POSIX
bc
, I have probably become one of few people in the world who can be
considered bc
experts. Two others are Philip A. Nelson, the author of the
GNU bc
, and Carl W. (last name unknown), aka Phodd, whose bc
libraries are second-to-none.
For anyone who thinks bc
is not very powerful, look at Carl’s work; the amount
of things he did is insane!
If you try to use his libraries with my bc
, you will get an error when loading
the file with abs()
because my bc
has abs()
built-in. Just remove his
implementation of abs()
.
Because of my work, I have learned a few tricks to get bc
and dc
to do what
I need. Here are some of them.
This is a living document. I will keep adding to it as time goes on.
These tips and tricks assume you are using my bc
and dc
built with the
PRNG implemented on the rand
branch.
Update (27 Feb 2022): The rand
branch has been merged for a long time; you
can use the latest version of bc
and dc
instead.
bc
Tips
Use Carl’s libraries.
Seriously. Just use them. They are extensive, well done, and have a lot of care put into them, even though Carl claims otherwise.
Use BC_ENV_ARGS
My bc
accepts an environment variable named BC_ENV_ARGS
. This is meant
to be set to the arguments you would pass to bc
on every invocation. For
example, users almost always want bc
to load the built-in library, which
requires the -l
option, so they should set the following:
export BC_ENV_ARGS="-l"
in their .bashrc
or equivalent.
My BC_ENV_ARGS
are as follows:
export BC_ENV_ARGS="-lg"
The -g
(--global-stacks
) option is there because I prefer being able to
set a global (ibase
, obase
, scale
, or seed
) in a function and not have
to set it back to the original value before returning from the function.
This leads me to:
Use the -g
Option
Seriously, it makes writing bc
code much easier. Otherwise, bc
will
“misbehave,” or in other words, it will do what you do not expect it to do.
Global variables will end up with seemingly random values, and it will be a pain
to debug.
This is the number one thing I think POSIX should adopt from my bc
.
Tricks
Generating an n
-bit Random Number
To generate an n
-bit unsigned random number, use the following:
irand(2^n)
To generate an n
-bit signed random number, use the following:
srand(irand(2^(n-1)))
Calculate the Number of Required Bytes
Ever wondered how many unsigned bytes are needed to hold a certain integer x
?
Use the following:
ubytes(x)
For signed, two’s-complement bytes, use the following:
sbytes(x)
To find the number of bits, just multiply the result(s) from above by 8.
dc
Tips
Use DC_ENV_ARGS
Like bc
, dc
has recognizes an environment variable called
DC_ENV_ARGS
. I don’t use dc
, but if I did, my DC_ENV_ARGS
would be set to:
export DC_ENV_ARGS="-x"
This leads me to:
Use the -x
Option
dc
has an option called -x
(--extended-register
) that allows dc
to
use (almost) arbitrary variable names (registers) by using extended
register mode.
The original dc
could only use one-letter variable names, but most dc
implementations have ways of extending this, though there are various ways of
doing so. For example, the BSD dc
allows two-character names.
In my not-so-humble opinion, my dc
has done it the best because variable names
can be as arbitrary as bc
’s variable names. The only thing that you must do is
put a space in-between the register command (e.g., l
, s
, L
, S
, and
logical comparisons) and the register name. Then, dc
will parse the next bit
as a variable name.
Learn How to Use Strings
In dc
, strings are enclosed in square brackets:
[This is a string!]
In my dc
, brackets can be escaped with backslash:
[This is a string with brackets \[\].]
But that is the easy part. The weirdest thing about dc
is that it doesn’t just
use strings for printing messages: it can also execute them.
1[plxx]dsxx
That code will go into an infinite loop printing the number 1
. Here is how it
works:
- The number
1
is pushed onto the stack. - The string
[plxx]
is pushed onto the stack. d
duplicates the string on the stack and pushes the duplicate onto the top of the stack.sx
pops the copy off of the top of the stack and stores it in the registerx
.x
pops the top item off of the stack, which was the original string, and executes it.- Since the string was popped off of the stack, the top of the stack is
1
, andp
prints it without removing it from the stack. lx
copies the contents of registerx
(which was the duplicated string) and pushes the copy onto the stack.x
pops the top item (the copy of registerx
), and executes it.dc
goes back to step 6.
With the capabilities that my dc
provides, you can use string execution as a
stand-in for loops, if statements, and function calls. In the right hands, dc
is just as powerful as bc
.
Tricks
Create an Infinite Loop
To create an infinite loop in dc
, just use the following formula:
- Decide what register you will store the string in. For the example below, I
have chosen
x
. - Create a string.
- Put
l<register>x
at the end of the string, where<register>
is replaced by the register you chose in step 1. - Put
ds<register>x
, where<register>
is also replaced as above, outside the string.
[<code> lxx]dsxx
You can quit an infinite loop with the Q
command.
Create a while
Loop
Creating a while
loop in dc
is a little more involved:
- Decide what register you will store the string in. For the example below, I
have chosen
x
again. - Figure out what condition the
while
loop will need. - Figure out how to implement the condition with boolean arithmetic commands and the conditional execution commands
- Put the code for the condition at the end of the string, with the appropriate conditional execution command.
- Use that same code to start executing the string or not.
[<code> <condition_code>x]sx <condition_code>x
Create a for
Loop
Creating a for
loop is just like creating a while
loop, except that you have
to add a counter:
- Decide what register you will store the string in. For the example below, I
have chosen
x
again. - Create a counter register and store the starting counter value in it.
- Store the string in the register.
- Put a condition at the end of the string load the counter, compare it to the end value, and execute again if the counter is too small.
- Put
ds<register>x
, where<register>
is replaced with the register that has the string, after the string.
0si [<code> 10 li <x]dsxx
Create an if
Statement
You can also use conditional execution commands to create if
statements:
<code> [<if_statement_body_code>]sx =x
The above example uses the =x
command to conditionally execute the string in
register x
.
You can even add an else
statement using this dc
’s support for them:
<code> [<if_body_code>]sx [<else_body_code>]sy =xey
Conclusion
There isn’t really any point to this post other than to demonstrate that you can
use bc
and dc
for anything a desktop calculator can do. I hope you find
these tips and tricks useful!
Also, feel free to send me more.