Decimal to binary and hexadecimal converter on the Apple ][ - Part 1

carlosbermejop

Carlos Bermejo Pérez

Posted on March 25, 2023

Decimal to binary and hexadecimal converter on the Apple ][ - Part 1

Imagine that you want to convert an integer to binary and hexadecimal, but you are so terribly lazy that the prospect of opening your calculator in programmer mode is inconceivable.

Screenshot of the Windows 11 built-in calculator in programmer mode displaying that 147 base 10 is equivalent to 0x93, 223 base 8 or 0b10010011

Getting the integer from the user and doing the conversion itself is just a couple of lines away in most modern programming languages. See this very simple implementation in Python as an example:

x = int(input("Please enter a decimal number between 0 and 255: "))
print(f"{x} is {bin(x)} in binary.")
Enter fullscreen mode Exit fullscreen mode

Therefore, what's going to be interesting to analyze is not how to do it now, but how could we have done it before. However, I'm going to have to take the longer route to get there. Not having received formal training in CS, I've always felt more like the holder of a driver's license, able to use code, but not actually like the engineer or the mechanic that actually understands how the guts of the beast work.

I have found that these sort of projects, useless as they might be in practical terms, are a great way to force myself to reinvent the wheel and get a more thorough understanding of the evolution of programming as a concept.

In this series we'll try to do the following:

  • Given an integer between 0 and 255 (so the result is a 1 byte binary number), get the binary and hexadecimal equivalents.
  • Implement the code in Applesoft BASIC as it would work on the Apple ][ using an emulator.
  • Convert the code to BASIC for the Commodore 64 (though this and the following point will have to wait for another day and will act as launching point to talk about assembly and machine code).
  • Compare the implementation in BASIC with a version of the code written in assembly.

So let's just dive into the Applesoft BASIC implementation for the Apple ][e.

To use the less annoying way to type the name, although it was trademarked using the square brackets, the Apple II was released to the public on June 1977. You can see in the cover image of this post a photograph of the computer taken by William Warby. Using the ubiquitous MOS Technology 6502 —the same microprocessor that powered the NES, the Atari 2600 or the Commodore 64—, and in contrast with Integer BASIC, Applesoft BASIC implements floating-point capabilities.

Of course, the best way to learn the syntax is to read through the fantastic Reference Manual that came with the computer, but there are two particularities that we'll need to code around:

  • naturally, we don't have a function that will directly convert an int to a base-2 or base-16 number;
  • although present in Integer BASIC, in Applesoft BASIC we don't have a module implementation.

Let's plan how to deal with both problems (which are actually related) by replicating the steps you would follow to do the conversion by hand. To convert a base-10 number to binary, we can follow these steps:

  1. Firstly, divide the number by 2 to find the quotient. For instance

q=147/2=73 q = 147 / 2 = 73
  1. Take note of the quotient, though for the second we won't need to worry about it.
  2. The remainder of the operation will be either 0 or 1. If you think about the operation as an integer division, all even numbers divided by two will have a remainder of 0 and viceversa with odd numbers resulting in a remainder of 1.

We will take advantage of this fact to implement a formula such as the following:

r=x((x/2)2) r = x - ((x / 2) * 2)

where rr is the remainder, and xx is (in this first iteration) the number that we want to convert. Now, what do we do with the result? It will be the binary number for position 0 of the binary number.
What is position 0? The right-most number in the complete figure. For instance, let's say that we want to find the conversion of 147 (base 10) to binary.

r=147((147/2)2)=147146=1 r = 147 - ((147 / 2) * 2) = 147 - 146 = 1

The last number will be a 1.

  1. Now let's go back to the quotient (which was 73). We'll start again from step 1, but using 73 as the number to divide. We will obtain position 1 (to the left of position 0).
  2. We will stop when the quotient is equal to 0.

The result will show that 147 base 10 equals 0b1001 0011.

Now, repeat all of these steps but dividing and multiplying by 16 and we'll get the hexadecimal conversion. I understand that, when done manually, it is easier to take each nybble (each group of four bits) and find the hex equivalent to find that 147 equals (1001, 8 + 1; 0011, 2 + 1) 0x93, but that assumption would actually result in a more costly implementation in Applesoft BASIC.

After this lengthy preamble, let's get to the meat of the matter, the code itself.

10 HOME : REM CLEAR THE SCREEN
20 PRINT "DECIMAL TO BINARY CONVERTER"
30 PRINT
Enter fullscreen mode Exit fullscreen mode

Those numbers to the left of the instructions? Those are telling the computer the order in which to execute the commands, and (unless you install some helper code) they need to be entered manually. The standard is to increment line numbers 10 by 10 so that new instructions can be added in between without having to reenter large chunks of code (that variable initialization you forgot after line 30? Put it in line 35.

These three lines clear the screen (HOME), print a title to the terminal and a blank line. REM indicates a code remark (so a comment, they are just here to document the code).

40 DIM P(7) : REM ARRAY P WITH DIMENSION FOR 8 ELEMENTS
50 INPUT "PLEASE ENTER A NO. BETWEEN 0 and 255: " ; X : REM X = NUMBER TO CONVERT
60 Q = X : REM Q WILL REPRESENT THE QUOTIENT
Enter fullscreen mode Exit fullscreen mode

We will map the result of our formula to each of the elements in the array P in reverse, so that P[7] will be position 0 (the actual syntax is P(7), but you know what I mean).

70 IF X = 0 GOTO 40 : REM ERROR CONTROL
80 IF X > 255 GOTO 40
Enter fullscreen mode Exit fullscreen mode

Conditional branches follow the IF... THEN... syntax, although THEN can be replaced in this case for GOTO, which will execute the code located at that line (in 40, we queried the user for the number). Notice that the operators themselves are pretty similar to what you would get in modern programming languages (!= would be <> or >< in Applesoft BASIC), although the equal sign is not duplicated (and, even so, not confused with the assignment operator).

90 FOR I = 7 TO 0 STEP - 1 : REM FOR LOOP IN REVERSE
100 R = 0 : REM INITIALIZE THE REMAINDER TO 0
110 R = Q - (INT (Q / 2) * 2) : REM CALCULATE R
120 P(I) = R : REM ASSIGN THE ELEMENT OF THE ARRAY AT INDEX I TO THE REMAINDER
130 Q = INT(Q / 2) : REM CALCULATE THE 
140 NEXT I
Enter fullscreen mode Exit fullscreen mode

This FOR... TO... STEP... loop uses a syntax closer to English than modern programming languages, and notice that the equivalent to a i++ or similar is in line 140 (NEXT I), which we could think of as similar to Python's yield. Also, notice that we are type casting the results of the calculations to integers.

150 PRINT X ; " IS " ; P(0) ; P(1) ; P(2) ; P(3) ; " " ; P(4) ; P(5) ; P(6) ; P(7) ; " IN BINARY."
Enter fullscreen mode Exit fullscreen mode

Is this the cleanest code I have ever written? The most efficient? I like to think it's not. However, when I got to this point and it worked, I felt like popping open a bottle of champagne. To quote the musical Falsettos, "even [I] can be copacetic! I think, I think... I think [I] can, I think [I] can".

I'll just paste here the rest of the code that deals with hexadecimal:

160 TEM$ = "0123456789ABCDEF" : REM WE NEED THIS STRING TO GET THE VALUE A TO F
170 Q = X
180 DIM H$(1) : REM THIS ARRAY IS ACTUALLY A STRING, BUT WE ARE SETTING THE SIZE
190 FOR I = 1 TO 0 STEP - 1
200 R = 0
210 R = Q - (INT (Q / 16) * 16)
220 H$(I) = MID$(TEM$, R, 1) : REM WE GET THE POSITION FOR STRING TEM$ LOCATED AT R AND THE RESULTING SUBSTRING IS JUST 1 CHR
230 Q = INT(Q / 16)
240 NEXT I
250 PRINT "AND " ; X ; " EQUALS 0x" ; H$(0) ; H$(1) ; " IN HEXADECIMAL."
260 END : REM STOP THE PROGRAM GRACEFULLY
Enter fullscreen mode Exit fullscreen mode

For this last segment of the code, just notice that the name of variables representing strings end in $.

You can find the complete code conveniently put together in a .a file and a .dsk image in my GitHub. You can use the .dsk on one of the many Apple ][ online emulators available, my recommendation is to use this one or, better still, to use AppleWin if you're on Windows. The disk image is a copy of their MASTER.DSK file, including this program.

To run the program, boot up whichever method of emulation you chose, browse your system for the .dsk file and execute the following commands:

  • LOAD DEC2BIN
  • LIST (to validate that you can see the code being printed to the terminal)
  • RUN
  • Enter a number to see the result.

There are many features of BASIC that went uncovered for this initial incursion into the language, including of course the concept of subroutines (think functions, but more as a set of instructions than the current concept), and the very powerful graphical mode, but this'll do for this first foray.

Next time we'll compare this BASIC code with the equivalent implementation for the Commodore 64, and we'll take our first tentative steps into machine language and assembly. See y'all around!

💖 💪 🙅 🚩
carlosbermejop
Carlos Bermejo Pérez

Posted on March 25, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related