My son recently asked me what the letters following, or under, the SuperBowl words were for. I explained about the game number and that the number was in roman numerals. Then offered what they meant though I couldn’t be definitive on what number they actually represent: XLIX. It has been a number of years since I last worked with roman numerals. So I decided to re-learn them, and in doing so ended up writing an Atari BASIC program to convert from integer to roman and vice-versa.
I started at the wiki page for Roman Numerals, http://en.wikipedia.org/wiki/Roman_numerals, to learn the values for letters I was unsure of like L and D. I also re-learned a few facts. Here they are along with the values and basic rules:
- A letter may not repeat more than 3 times.
- 3,999 is the largest number that can be represented without “new” tricks involving implied multiplication.
- 0 is not represented.
- I = 1
- V = 5
- X = 10
- L = 50
- C = 100
- D = 500
- M = 1000
- V can be proceeded by I as in “IV” to denote 4.
- X can be proceeded by I as in “IX” to denote 9.
- L can be proceeded by X as in “LX” to denote 40.
- C can be proceeded by X as in “XC” to denote 90.
- D can be proceeded by C as in “CD” to denote 400.
- M can be proceeded by C as in “CM” to denote 900.
Armed with conversion rules, values, and a few example conversions I set out to write a program in Atari BASIC to convert from Integer to Roman Numeral. Once done I decided to write the reverse, from Roman Numeral to Integer. I’m sure there are other ways, perhaps some that are more efficient, but this is what I came up with.
Integer to Roman
The process for converting from integer (2015) to Roman Numerals (MMXV) is fairly simple.
- Check the value of the integer against the known roman numeral values, such as 1000 for M, starting with highest values and working to the smallest. If the integer exceeds or equals it, add the roman numeral to the conversion string and subtract the amount from the integer.
- Repeat until the integer equals 0.
Roman to Integer
The process for converting from Roman Numerals (MMXV) to integer (2015) is a little more complex, but still fairly simple.
- Start at the end of the roman numeral and work toward the first position.
- Check the value of the roman numeral at the string position. With my method I can’t just straight convert the value to it’s integer equivalent for all values. I need to check for any qualifier numerals in the position immediately to the left. For cases like IV (4), which in essence means 1 from 5 (5-1). So most cases, other than I (1) will have two checks; the first to check for the qualifier, the second to check for just the roman numeral (straight conversion).
- Repeat until all string positions of the roman numeral have been exhausted (string position 1 is reached).
Source Code
One thing to note is that I did not code any sanity checks. Garbage in will equal garbage out. You can also create invalid roman numerals both directions, but if you stick to 1 through 3999 (and the corresponding roman values) the results will be accurate.
The only thing I changed here for the blog is to convert the ATASCII characters which cannot be represented here to ASCII characters. This involved the header line (20) and the menu line (110).
10 ? CHR$(125):POKE 82,0 20 POSITION 0,0:? "--|Roman Converter|---------------------"; 50 ILOOP=1000:IEND=1150:RLOOP=2000:REND=2200:MLOOP=100:MEND=300:INPLOOP=120:INPEND=200 52 POS=1:ITOR=1:RTOI=2:CDIR=0:V=0:K=0 56 DIM R$(20),I$(20) 100 REM *** MLOOP *** 105 I$="":V=0 110 ? CHR$(155);"Integer, Roman, Exit (IRE)?"; 120 REM *** INPLOOP *** 130 K=PEEK(764) 140 IF K<>13 AND K<>77 AND K<>40 AND K<>104 AND K<>22 AND K<>86 THEN GOTO INPLOOP 150 IF K=13 OR K=77 THEN CDIR=ITOR:GOTO INPEND 160 IF K=40 OR K=104 THEN CDIR=RTOI:GOTO INPEND 170 IF K=22 OR K=86 THEN GOTO MEND 180 GOTO INPLOOP 200 REM INPEND 202 POKE 764,255:? "" 204 IF CDIR=ITOR THEN ? "Integer";:GOTO 210 206 ? "Roman Numeral"; 210 INPUT I$ 220 IF CDIR=ITOR THEN V=VAL(I$):R$="":POS=1:GOSUB ILOOP:? "Roman=";R$:GOTO MLOOP 230 IF CDIR=RTOI THEN R$=" ":R$(2)=I$:I$="":POS=LEN(R$):GOSUB RLOOP:I$=STR$(V):? "Integer=";I$:GOTO MLOOP 300 REM *** MEND *** 305 POKE 764,255 310 END 1000 REM *** ILOOP (I TO R) *** 1010 IF V>=1000 THEN R$(POS)="M":V=V-1000:POS=POS+1:GOTO ILOOP 1020 IF V>=900 THEN R$(POS)="CM":V=V-900:POS=POS+2:GOTO ILOOP 1030 IF V>=500 THEN R$(POS)="D":V=V-500:POS=POS+1:GOTO ILOOP 1035 IF V>=400 THEN R$(POS)="CD":V=V-400:POS=POS+2:GOTO ILOOP 1040 IF V>=100 THEN R$(POS)="C":V=V-100:POS=POS+1:GOTO ILOOP 1050 IF V>=90 THEN R$(POS)="XC":V=V-90:POS=POS+2:GOTO ILOOP 1060 IF V>=50 THEN R$(POS)="L":V=V-50:POS=POS+1:GOTO ILOOP 1065 IF V>=40 THEN R$(POS)="XL":V=V-40:POS=POS+2:GOTO ILOOP 1070 IF V>=10 THEN R$(POS)="X":V=V-10:POS=POS+1:GOTO ILOOP 1080 IF V=9 THEN R$(POS)="IX":V=V-9:POS=POS+2:GOTO IEND 1090 IF V>=5 THEN R$(POS)="V":V=V-5:POS=POS+1:GOTO ILOOP 1100 IF V=4 THEN R$(POS)="IV":V=V-4:POS=POS+2:GOTO IEND 1110 IF V>=1 THEN R$(POS)="I":V=V-1:POS=POS+1:GOTO ILOOP 1120 IF V=0 THEN GOTO IEND 1150 REM IEND 1160 RETURN 2000 REM *** RLOOP (R TO I) *** 2005 IF POS=1 THEN GOTO REND 2010 IF R$(POS,POS)="I" THEN V=V+1:POS=POS-1:GOTO RLOOP 2020 IF R$(POS,POS)="V" AND R$(POS-1,POS-1)="I" THEN V=V+4:POS=POS-2:GOTO RLOOP 2030 IF R$(POS,POS)="V" AND R$(POS-1,POS-1)<>"I" THEN V=V+5:POS=POS-1:GOTO RLOOP 2040 IF R$(POS,POS)="X" AND R$(POS-1,POS-1)="I" THEN V=V+9:POS=POS-2:GOTO RLOOP 2050 IF R$(POS,POS)="X" AND R$(POS-1,POS-1)<>"I" THEN V=V+10:POS=POS-1:GOTO RLOOP 2060 IF R$(POS,POS)="L" AND R$(POS-1,POS-1)="X" THEN V=V+40:POS=POS-2:GOTO RLOOP 2070 IF R$(POS,POS)="L" AND R$(POS-1,POS-1)<>"X" THEN V=V+50:POS=POS-1:GOTO RLOOP 2080 IF R$(POS,POS)="C" AND R$(POS-1,POS-1)="X" THEN V=V+90:POS=POS-2:GOTO RLOOP 2090 IF R$(POS,POS)="C" AND R$(POS-1,POS-1)<>"X" THEN V=V+100:POS=POS-1:GOTO RLOOP 2100 IF R$(POS,POS)="D" AND R$(POS-1,POS-1)="C" THEN V=V+400:POS=POS-2:GOTO RLOOP 2110 IF R$(POS,POS)="D" AND R$(POS-1,POS-1)<>"C" THEN V=V+500:POS=POS-1:GOTO RLOOP 2120 IF R$(POS,POS)="M" AND R$(POS-1,POS-1)="C" THEN V=V+900:POS=POS-2:GOTO RLOOP 2130 IF R$(POS,POS)="M" AND R$(POS-1,POS-1)<>"C" THEN V=V+1000:POS=POS-1:GOTO RLOOP 2200 REM REND 2210 RETURN
Breakdown
- 10: Clear screen and set left margin to 0
- 20: Print the header line “-|Roman Converter|———————“
- 50 to 56: Setup variables. Line 50 contains program label markers to make reading the code easier to follow.
- 100: Start of the main loop, label MLOOP.
- 105: Set I$ which is the integer string to empty, and set V which is the computed value to 0.
- 110: Print a carriage return and the menu line.
- 120: Start of the input loop, label INPLOOP.
- 130: Grab the current value in memory location 764 which is the last key pressed register.
- 140: If the key press value is not upper or lower for I, R, or E, go back to the start of the input loop (INPLOOP).
- 150: If the key press value was upper or lower I then set the conversion direction flag to ITOR (Integer TO Roman). ITOR was initialized to 1 in line 52. Then go to the end of the input routine (INPEND).
- 160: If the key press value was upper or lower R then set the conversion direction flag to RTOI (Roman TO Integer). RTOI was initialized to 2 in line 52. Then go to the end of the input routine (INPEND).
- 170: If the key press value was upper or lower X then go to the end of the main loop (MEND).
- 180: The code logic above dictates this line should never be reached. Just in case, push control back to the start of the input loop (INPLOOP).
- 200: The end of the input routine (MEND). If the program gets to this point a valid key press of upper or lower I or R has been detected.
- 202: Put 255 in the last key pressed register to clear it and prevent the key press from feeding into the next keyboard input. Print empty string to bring cursor to next line.
- 204: If conversion direction (CDIR) is 1 (ITOR) print “Integer” and goto line 210.
- 206: Conversion direction must be RTOI so print “Roman Numeral”.
- 210: Get a string from the user into I$. If the user chose Integer they would enter an integer number like 2015. If they user choose Roman they would enter a roman numeral like MMXV.
- 220: If the conversion direction (CDIR) is 1 (ITOR) then:
- Set value (V) to the value of the string the user input (I$).
- Set the roman numeral string (R$) to empty.
- Set the string position counter (POS) to 1.
- Call the subroutine ILOOP to do the integer to roman conversion.
- Print “Roman=” and the converted value which is now in R$.
- Go to the main loop (MLOOP).
- 230: If the conversion direction (CDIR) is 2 (RTOI) then:
- Set R$ to space (” “). The string needs to be padded so the index is not overrun.
- Set R$(2), position 2, to the input string (I$). If the user entered “MMXV”, R$ would now equal ” MMXV”.
- Set I$ to empty value.
- Set the string position to the end of R$.
- Call the subroutine RLOOP to do the roman to integer conversion.
- Convert the returned value (V) to a string and store it in I$.
- Print “Integer=” and the converted value which is now in I$.
- Go to the main loop (MLOOP).
- 300: End of the main loop (MEND).
- 305: Put 255 in the last key pressed register (in case user pressed x) so it won’t be fed into the next keyboard input.
- 310: End the program
- 1000: Start of the Integer to Roman Numeral conversion routine (ILOOP).
- 1010: If the value (V) is greater than or equal to 1000, place roman numeral “M” in R$ at the current string position (POS). Subtract 1000 from value (V). Increase string position (POS) by 1. Goto start of conversion routine (ILOOP). This will repeat until the value (V) is less than 1000.
- 1020: If the value (V) is greater than or equal to 900, place roman numerals “CM” in R$ at the current string position (POS). Subtract 900 from value (V). Increase string position (POS) by 2. Goto start of conversion routine (ILOOP).
- 1030: If the value (V) is greater than or equal to 500, place roman numeral “D” in R$ at the current string position (POS). Subtract 500 from value (V). Increase string position (POS) by 1. Goto start of conversion routine (ILOOP).
- 1035: If the value (V) is greater than or equal to 400, place roman numerals “CD” in R$ at the current string position (POS). Subtract 400 from value (V). Increase string position (POS) by 2. Goto start of conversion routine (ILOOP).
- 1040: If the value (V) is greater than or equal to 100, place roman numeral “C” in R$ at the current string position (POS). Subtract 100 from value (V). Increase string position (POS) by 1. Goto start of conversion routine (ILOOP).
- 1050: If the value (V) is greater than or equal to 90, place roman numerals “XC” in R$ at the current string position (POS). Subtract 90 from value (V). Increase string position (POS) by 2. Goto start of conversion routine (ILOOP).
- 1060: If the value (V) is greater than or equal to 50, place roman numeral “L” in R$ at the current string position (POS). Subtract 50 from value (V). Increase string position (POS) by 1. Goto start of conversion routine (ILOOP).
- 1065: If the value (V) is greater than or equal to 40, place roman numerals “XL” in R$ at the current string position (POS). Subtract 40 from value (V). Increase string position (POS) by 2. Goto start of conversion routine (ILOOP).
- 1070: If the value (V) is greater than or equal to 10, place roman numeral “X” in R$ at the current string position (POS). Subtract 10 from value (V). Increase string position (POS) by 1. Goto start of conversion routine (ILOOP).
- 1080: If the value (V) is equal to 9, place roman numerals “IX” in R$ at the current string position (POS). Subtract 9 from value (V). Increase string position (POS) by 2. Since this is an absolute check and in the single digits there is no need to continue the loop so instead goto end of the conversion routine (IEND).
- 1090: If the value (V) is greater than or equal to 5, place roman numeral “V” in R$ at the current string position (POS). Subtract 5 from value (V). Increase string position (POS) by 1. Goto start of conversion routine (ILOOP).
- 1100: If the value (V) is equal to 4, place roman numerals “IV” in R$ at the current string position (POS). Subtract 4 from value (V). Increase string position (POS) by 2. Since this is an absolute check and in the single digits there is no need to continue the loop so instead goto end of the conversion routine (IEND).
- 1110: If the value (V) is greater than or equal to 1, place roman numeral “I” in R$ at the current string position (POS). Subtract 1 from value (V). Increase string position (POS) by 1. Goto start of conversion routine (ILOOP).
- 1120: If the value (V) is 0 goto end of the conversion routine (IEND).
- 1150 to 1160: End of the conversion routine (IEND). Return from subroutine.
- 2000: Start of Roman Numeral to Integer conversion routine (RLOOP).
- 2005: If the current string position is 1 goto the end of the conversion routine (REND).
- 2010: If the character at the current position (POS) of R$ is “I”, add 1 to value (V). Decrease string position (POS) by 1. Goto start of conversion routine (RLOOP).
- 2020: If the character at the current position (POS) of R$ is “V” and the character immediately to the left (POS-1) is “I”, add 4 to value (V). Decrease string position (POS) by 2. Goto start of conversion routine (RLOOP).
- 2030: If the character at the current position (POS) of R$ is “V” and the character immediately to the left (POS-1) is NOT “I”, add 5 to value (V). Decrease string position (POS) by 1. Goto start of conversion routine (RLOOP).
- 2040: If the character at the current position (POS) of R$ is “X” and the character immediately to the left (POS-1) is “I”, add 9 to value (V). Decrease string position (POS) by 2. Goto start of conversion routine (RLOOP).
- 2050: If the character at the current position (POS) of R$ is “X” and the character immediately to the left (POS-1) is NOT “I”, add 10 to value (V). Decrease string position (POS) by 1. Goto start of conversion routine (RLOOP).
- 2060: If the character at the current position (POS) of R$ is “L” and the character immediately to the left (POS-1) is “X”, add 40 to value (V). Decrease string position (POS) by 2. Goto start of conversion routine (RLOOP).
- 2070: If the character at the current position (POS) of R$ is “L” and the character immediately to the left (POS-1) is NOT “X”, add 50 to value (V). Decrease string position (POS) by 1. Goto start of conversion routine (RLOOP).
- 2080: If the character at the current position (POS) of R$ is “C” and the character immediately to the left (POS-1) is “X”, add 90 to value (V). Decrease string position (POS) by 2. Goto start of conversion routine (RLOOP).
- 2090: If the character at the current position (POS) of R$ is “C” and the character immediately to the left (POS-1) is NOT “X”, add 100 to value (V). Decrease string position (POS) by 1. Goto start of conversion routine (RLOOP).
- 2100: If the character at the current position (POS) of R$ is “D” and the character immediately to the left (POS-1) is “C”, add 400 to value (V). Decrease string position (POS) by 2. Goto start of conversion routine (RLOOP).
- 2110: If the character at the current position (POS) of R$ is “D” and the character immediately to the left (POS-1) is NOT “C”, add 500 to value (V). Decrease string position (POS) by 1. Goto start of conversion routine (RLOOP).
- 2120: If the character at the current position (POS) of R$ is “M” and the character immediately to the left (POS-1) is “C”, add 900 to value (V). Decrease string position (POS) by 2. Goto start of conversion routine (RLOOP).
- 2130: If the character at the current position (POS) of R$ is “M” and the character immediately to the left (POS-1) is NOT “C”, add 1000 to value (V). Decrease string position (POS) by 1. Goto start of conversion routine (RLOOP).
- 2200 to 2210: End of conversion routine (REND). Return from subroutine.
Results
Here the program was just started and sitting at the menu:
Here are samples of the program converting from Integer to Roman Numeral:
Here are samples of the program converting from Roman Numeral to Integer:
Here is the program converted to and from 49 and XLIX, which is the current SuperBowl, and choosing x to exit.