The binhex.tex package for expansible conversion into binary-based number systems

David Kastrup^{∗}

May 25, 2002

Contents

1 Usage 1

2 Implementation 3

2.1 Binary conversions . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 2.1.1 Basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 2.1.2 Negative numbers . . . . . . . . . . . . . . . . . . . . . . . 4 2.1.3 Minimum field widths . . . . . . . . . . . . . . . . . . . . . 4

2.2 Hexadecimal and the rest . . . . . . . . . . . . . . . . . . . . . . . 5 2.3 The end . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

3 Various drivers 7

1 Usage

This is a file for expandably converting numbers into binary, octal and hexadecimal. All constructs TEX accepts as an argument to its \number primitive are valid. This holds for all numeric arguments of the macros presented in here.

You use this package by simply inputting it with

\input binhex

It will work equally well under L^{A}TEX and plain TEX. It does not even use plain TEX, but only TEX primitives. Simply setting the correct \catcode values for {}# and end of line will make it load and work under iniTEX.

\binary The following macros are defined: \binary{⟨number⟩} will convert ⟨number⟩ into its binary representation.

∗David.Kastrup@neuroinformatik.ruhr-uni-bochum.de

                  \binary{0} → 0
          \binary{\maxdimen} → 111111111111111111111111111111
             \binary{-"7EE6} → -111111011100110

\nbinary \nbinary{⟨size⟩}{⟨number⟩} will convert ⟨number⟩ into a binary representation of at least ⟨size⟩ digits length, filling up with leading zeros where necessary. The - sign of negative numbers is not counted. If both ⟨size⟩ and ⟨number⟩ are zero, an empty string is generated. This should please some computer scientists in some situations.

                          \nbinary{3}{3} → 011
                         \nbinary{3}{-2} → -010
                        \nbinary{3}{-12} → -1100

\hex \hex{⟨number⟩} converts ⟨number⟩ into its hexadecimal representation, using

uppercase letters.

                          \begin{aligned}\hex\{34\} & → 22 \\ \hex\{-4711\} & → -1267 \end{aligned}

\nhex \nhex{⟨size⟩}{⟨number⟩} will convert ⟨number⟩ into a hexadecimal representation of at least ⟨size⟩ digits length, filling up with leading zeros where necessary. The - sign of negative numbers is not counted. If both ⟨size⟩ and ⟨number⟩ are zero, an empty string is generated. This should please some computer scientists in some situations.

                             \nhex{3}{3} → 003
                    \nhex{3}{-\maxdimen} → -3FFFFFFF

\oct \oct{⟨number⟩} converts ⟨number⟩ into its octal representation.

                          \begin{aligned}\oct\{34\} & → 42 \\ \oct\{-4711\} & → -11147 \end{aligned}

\noct \noct{⟨size⟩}{⟨number⟩} will do the right thing.

                           \noct{3}{13} → 015
                   \noct{3}{-\maxdimen} → -7777777777

\tetra \tetra{⟨number⟩} is for people counting with arms and legs instead of fingers,

or for quadrupeds.

                           \tetra{34} → 202
                        \tetra{-4711} → -1021213

\ntetra \ntetra{⟨size⟩}{⟨number⟩} is for those of the same count which have minimum requirements.

                        \ntetra{3}{3} → 003
               \ntetra{3}{-\maxdimen} → -333333333333333

\nbinbased \nbinbased{⟨logbase⟩}{⟨size⟩}{⟨number⟩} will convert ⟨number⟩ into number base 2^{⟨logbase⟩} and generate at least ⟨size⟩ digits. Only supported values of ⟨logbase⟩ are 1, 2, 3, 4. This is called by all other macros except of the faster binary conversion macros.

                       \nbinbased{3}{3}{13} → 015
               \nbinbased{3}{3}{-\maxdimen} → -7777777777
                    \nbinbased{2}{4}{13} → 0031
            \nbinbased{2}{4}{-\maxdimen} → -333333333333333

That’s it, have fun!

2 Implementation

Now the implementation. First save catcode of @ and old contents of \toks0 in \toks0, then make @ a letter to enable internal macros.

1 ⟨∗style⟩

 2 \edef\next{\toks0=%
 3 {\catcode`\noexpand\@=\the\catcode`\@\toks0{\the\toks0}}%
 4 }
 5 \next
 6 \catcode`\@11

2.1 Binary conversions

2.1.1 Basics

\bb@00 \bb@01 ... \bb@19 We now define the work horse macros for conversion of binary commands. If numbers were allowed in macro names, one of those definitions would be

\def\bb@13#1{6\csname bb@1#1\endcsname}

That is, the macro divides the decimal two-digit number (up to 19) embedded into its name by 2, spews out the result and starts up the next macro with the first digit of the name of that being the remainder from its own division, and the second digit being the next following one.

 7 \def\next#1#2#3{\expandafter \def \csname bb@#1\endcsname##1%
 8 {#2\csname bb@#3##1\endcsname}}
 9 \next{00}00 \next{01}01 \next{02}10 \next{03}11
10 \next{04}20 \next{05}21 \next{06}30 \next{07}31
11 \next{08}40 \next{09}41 \next{10}50 \next{11}51
12 \next{12}60 \next{13}61 \next{14}70 \next{15}71
13 \next{16}80 \next{17}81 \next{18}90 \next{19}91

{\bb@0+ \atop \bb@1+} Now we need to end such a conversion. For no particular reason at all (well, some sneakiness might explain it later), we use + as an end marker for the decimal digits. After the +, we collect the resulting binary digits, from last to first. So here are two macros to deal with the last digit produced. Simply tacks them on after replacing the gobbled +.

14 \expandafter \def \csname bb@0+\endcsname {+0}
15 \expandafter \def \csname bb@1+\endcsname {+1}

Now all that remains to be done is to initiate the process, and to end it again. The process ends when the decimal number reaches 0. Since we want to produce one digit even when starting out, and we check the end of recursion by comparing the result with 0, we have a problem here. We solve it by comparing two letters, and looking for the complete 0+ sequence, and we start out by prepending a trivial 0 before the number to convert, so that at the first run it will be 00 instead of 0 if the number in question is 0.

(\binary) (\bb@dobinary) Actually, this is how we could do things. The actual, slightly different version used can be shared by the other converters, but we’ll keep this listed for reference.

\def\binary#1{\expandafter \bb@dobinary \expandafter 0\number#1+}
\def\bb@dobinary#1#2{\if0#1\if+#2\bb@endbinary \fi\fi
   \expandafter \bb@dobinary \number \csname bb@0#1\endcsname #2}

Notice that \number here serves multiple purposes. It will initiate expansion that will only be stopped once the macros generating the next binary digit will crank out a space or anything else stopping expansion. Its second purpose is to get rid of any leading zeros that might remain from the last loop through.

\bb@endbinary \bb@endbinary can be used as written, though. It scraps everything up to the tack mark +, leaving only the converted results accumulated behind that. Since this means scrapping \fi\fi as well, it reinserts it in order to properly finish the conditional.

16 \def\bb@endbinary#1+{\fi\fi}

2.1.2 Negative numbers

We would like negative numbers to work, too. The semantics for defined field widths are unclear (so we basically just tack a - on and convert the remaining number to the full width). Since the sign of the number is easy to discern, further massaging of the number to replace leading zeros by spaces, insert + signs and similar folderol are easy to do and left as an exercise to the reader. We mostly implement this to have the conversion not fail on any number.

\bb@0- The following one-liner achieves that. Figure out why. It is easy.

17 \expandafter \def \csname bb@0-\endcsname {0+-\bb@dobinary}

2.1.3 Minimum field widths

{\bb@0m \atop \bb@1m} We often have the situation that we want to produce a number that is at least of a certain length. We specify this by tagging on an appropriate number of m characters to the decimal as sort of a format string. If we match such an m at the end of the conversion, we produce a digit and remove one m, leaving all other m intact (or whatever else happens to be before our + sign).

18 \expandafter\def\csname bb@0m\endcsname#1+{#1+0}
19 \expandafter\def\csname bb@1m\endcsname#1+{#1+1}

(\nbinary) We call \nbinary with the number of digits as first argument, the number itself

as second.

This now becomes trivial:

\def \nbinary#1#2{%
  \binary{\number #2\romannumeral \number \number #1 000}%
}

Whoa, what about all these calls of \number? First notice the space after #1. We need that in case #1 happens to be octal or whatever, since the 000 at the end is supposed to multiply by 1000 decimal. If #1 were something like '13 (11 decimal), the last \number will expand '13, the \number before that will remove the blank we inserted in case it was not needed to finish the number of digits, we multiply by 1000 and turn this into a roman numeral consisting of the appropriate amount of m letters. The very first \number ensures that in case #2 is a complete number without the need of trailing spaces, still \romannumeral will be expanded when \binary first sets sight on the whole thing.

\nbinary \binary \bb@dobinary Actually, since there is a certain logic to returning an empty string when 0 is to be converted into a number at least 0 characters long, we redefine the stuff the other way round, \binary as a special case of \nbinary. We check the end by testing against 0 followed by anything but m. This has reasons…

20 \def\bb@dobinary#1#2{\if#10\if m\string#2\else\bb@endbinary\fi\fi
21 \expandafter\bb@dobinary\number\csname bb@0#1\endcsname#2}
22 \def\nbinary#1#2{\expandafter\bb@dobinary\number\number#2%
23 \romannumeral\number\number#1 000+}
24 \def\binary{\nbinary1}

2.2 Hexadecimal and the rest

Ok, stop with the small fry. Now we want to convert into hexadecimal and octal as well. We do this by first converting into binary, then into the wanted base whenever enough binary digits have accumulated. In the following, we will talk about hex digits exclusively for simplicity, even though they might indeed be octal instead.

\bb@h0 \bb@h1 \bb@h10 ... \bb@h1111 The following defines the equivalents of

\def \bb@h110#1+{\bb@dohex #1+6}

This is a macro that appends 6, the representation of the binary number in its name, after the tack mark +. So what’s with the \number+? Actually, here it does nothing but disappear. We just write this to remind us of how the macros will be called. When the macros are called, we use the same construct, and then \number will disappear together with the + (that we use as a tack mark) and take along any leading zeros. We drop leading zeros so that we can share the conversion macros for hex and octal (and quaternary?) where they overlap.

25 \def \next #1#2{\expandafter \def
26 \csname bb@h\number +#1\endcsname ##1+{\bb@dohex ##1+#2}%

27 }

28 \next  {0}0 \next  {1}1 \next {10}2 \next {11}3
29 \next {100}4 \next {101}5 \next {110}6 \next {111}7
30 \next{1000}8 \next{1001}9 \next{1010}A \next{1011}B
31 \next{1100}C \next{1101}D \next{1110}E \next{1111}F

\bb@dohex \bb@dohex is the magic macro that initiates accumulation of enough binary digits for one hexadecimal one. It is called in the following form:

     \bb@dohex ⟨m{#rhex}⟩ \endcsname ⟨m{#bin}⟩ +

Where + is the tack after which hex digits will accumulate, ⟨m{#rhex}⟩ is a sequence of m, one for every remaining hex digit we want to force out (same convention as with \nbinary above), and ⟨m{#bin}⟩ is a sequence of m corresponding in length to the number of binary digits we need to accumulate for one hexadecimal (or octal) digit.

32 \def\bb@dohex #1{\csname bb@x#1\endcsname}

Eeek! What is that? Ok, let’s split it into the two cases, one with forced digits remaining, one without any remaining forced digits. If no forced digits remain, we get

     \bb@x \endcsname \endcsname ⟨m{#bin}⟩
\bb@x  \bb@x is defined as
33 \def\bb@x\endcsname#1{ \bb@xm{m\endcsname}}

Notice the space at the start of the macro! It will stop expansion. Furthermore, the \endcsname that \bb@dohex inserted is scrapped, as well as the first m from ⟨m{#bin}⟩.^{1} \bb@xm The argument fed to \bb@xm is part of some jiggery-pokery we want to happen

1Why do we match \endcsname explicitly for scrapping? The answer is debugging. We know it should always be \endcsname, so let TEX assert that it is indeed so. But we also know that the next character will be m, why don’t we match that explicitly? The answer is that we cannot be sure about the \catcode of the matched m. A hand-entered m has \catcode 11, a TEX-generated one (with \string or \romannumeral or such) has \catcode 12. You’ll find that all the code here has been carefully designed so that it will not care which it gets, so we don’t make an exception here.

when \bb@dobinary resumes expansion. First, however, it will take a look and decide whether it will stop generating digits altogether. As \string \bb@xm does not start with m, \bb@dobinary will stop expansion when the decimal to convert has shrunk to 0. Fine. Now what does \bb@xm do?

34 \def\bb@xm #1\endcsname #2#3+{#2#3%
35 \csname bb@h\number+\endcsname
36 #1\endcsname m#3+}

This is rather straightforward for the case of forced digits: the number of binary digits is encoded in the form of ⟨m{#bin}⟩ in #2#3. After the conversion of those digits, the appropriate hex digit macro is called and sets up \bb@dohex again for the next hex digit. Piece of cake. Now what happens in the case we have resumed from \do@binary having had its option of stopping expansion?

In that case, we get called with the next ⟨digit⟩ already arriving. This looks more or less like

     \csname bb@⟨digit⟩\bb@xm \endcsname{m\endcsname} ⟨m{#bin-1}⟩

See what happens? This expands \bb@xm, after which a command of the name \bb@⟨digit⟩m (⟨digit⟩ being 0 or 1) gets executed. It stashes away ⟨digit⟩ after the conveniently provided + sign, and ⟨#bin-1 ⟩ digits remain for conversion.

You think this contrived? Well, buster, let me tell you that the previous version was way more insane. Have it still in RCS.

\bb@nbinbased The following macro needs to get #1, the decimal number to convert, in text form already. This is so that \number#1 will continue expanding after #1, expanding \bb@dohex exactly once. This problem does not exist for the \romannumeral calls for #2 and #3, so spurious expansion with \number\number does no harm, since it is stopped readily at the hard \endcsname and + thresholds.

37 \def\bb@nbinbased #1#2#3{\expandafter \bb@dobinary \number#1%
38 \expandafter \bb@dohex
39 \romannumeral \number\number #2 000\expandafter\endcsname
40 \romannumeral \number\number #3 000+}

\nbinbased The following macro gets 3 arguments, the number of binary digits the output digits shall be based on, the number of digits to produce at least, and the number itself. It expands the number into text form. If the number is not space-terminated, the closing brace reliably stops expansion nevertheless, so that \bb@nbinbased gets a clean number.

41 \def\nbinbased #1#2#3{\expandafter\bb@nbinbased
42 \expandafter {\number#3}{#2}{#1}}
\nhex \noct \ntetra \hex \oct \tetra Now follow the obvious definitions:
43 \def\nhex{\nbinbased4}
44 \def\noct{\nbinbased3}
45 \def\ntetra{\nbinbased2}
46 \def\hex{\nhex1}

47 \def\oct{\noct1}
48 \def\tetra{\ntetra1}

2.3 The end

Restore \catcode`\@ and \toks0 and finish.

49 \the\toks0

50 ⟨/style⟩

And that was that!

3 Various drivers

The installer, in case it is missing.

51 ⟨installer⟩ \input docstrip

52 ⟨installer⟩ \generate{

53 ⟨installer⟩ \file{binhex.drv}{\from{binhex.dtx}{driver}}
54 ⟨installer⟩ \usedir{tex/generic/misc}
55 ⟨installer⟩ \file{binhex.tex}{\from{binhex.dtx}{style}}

56 ⟨installer⟩ }

57 ⟨installer⟩ \endbatchfile

And here comes the documentation driver.

58 ⟨driver⟩ \documentclass{ltxdoc}
59 ⟨driver⟩ \usepackage{amsmath}
60 ⟨driver⟩ \input binhex.tex

\verbatize \detokenize is available in eTEX, but we may not have that… The \fontdimen folderol makes spaces temporarily disabled. Looks prettier. Sorry for that.

61 ⟨driver⟩ \def\verbatize#1{\begingroup
62 ⟨driver⟩ \toks0{#1}\edef\next{\the\toks0}%
63 ⟨driver⟩ \dimen0\the\fontdimen2\font
64 ⟨driver⟩ \fontdimen2\font=0pt
65 ⟨driver⟩ \expandafter\stripit
66 ⟨driver⟩ \meaning\next
67 ⟨driver⟩ \fontdimen2\font=\dimen0
68 ⟨driver⟩ \endgroup}
69 ⟨driver⟩ \def\next{}
70 ⟨driver⟩ \expandafter\def\expandafter\stripit\meaning\next{}

\showeffect \showeffect⟨Stuff ⟩ will put out a one-line correspondence of the verbatim source of ⟨Stuff ⟩ with its expansion, to be used in a aligned environment or similar.

71 ⟨driver⟩ \newcommand\showeffect[1]{%
72 ⟨driver⟩ \text{\verbatize{#1}}\quad&\rightarrow\quad\text{#1}%

73 ⟨driver⟩ \\}

\samples \samples gets one parameter, and a following expression list ended by a period. It applies that parameter to each of the expressions in the list and generates a correspondance table for each. For example,

\samples{\nbinary{3}}{3}{-2}{-12}.

was used in this document to produce

                          \nbinary{3}{3} → 011
                         \nbinary{3}{-2} → -010
                        \nbinary{3}{-12} → -1100

74 ⟨driver⟩ \def\samples{\begingroup\MacroFont\[\begin{aligned}\nextsample}
75 ⟨driver⟩ \def\nextsample#1#2{%
76 ⟨driver⟩ \if.\noexpand#2\expandafter\endsamples\fi
77 ⟨driver⟩ \showeffect{#1{#2}}\nextsample{#1}}
78 ⟨driver⟩ \def\endsamples#1\nextsample#2{\end{aligned}\]\endgroup}

Now all that remains is inputting the stuff.

79 ⟨driver⟩ \begin{document}
80 ⟨driver⟩ \DocInput{binhex.dtx}
81 ⟨driver⟩ \end{document}