Wednesday, September 10, 2014
When in Rome
Blog Entry #334
Over and above the New Array Text Pages scripts, Jenson's Easy to Use JavaScripts directory features a Roman Numeral (RomanNumerals.html) Script that I thought we would look at in today's post. The RomanNumerals.html script
(1) converts Arabic numerals to Roman numerals and also
(2) creates a date string whose day and year numbers are expressed in Roman numerals.
On the whole the RomanNumerals.html script is well-designed but it does have one serious flaw, namely, it's not interactive: a corrective demo will be provided at the end of the post.
Numeric inputs
Specifically, the RomanNumerals.html script converts Arabic integers in the range 1-3999 inclusive to their Roman numeral equivalents.
• For a 0 input the script outputs an empty string.
• Floating-point inputs are Math.floor( )ed, e.g., 3.14159 gives III.
• Oddly, the script accommodates negative integer inputs in the range -1 to -3999 inclusive - -25 gives -XXV, e.g. - even though Roman numerals are not really meant for negative numbers.
• For integers larger than 3999 (or smaller than -3999) the script outputs an "Out of Range" string.
Wikipedia's "Roman numerals" entry includes a Special values section that addresses the Roman approaches to zero, fractions, and large numbers. Meanwhile, wikiHow reports that
the Romans themselves just wrote MMMM for 4000.
The cast of characters
In his script commentary, Jenson correctly notes:
Roman numerals are based on only two basic digits: 1 and 5.
However, they use different characters depending on the power of 10 being represented.
Roman Numeral | Arabic Equivalent | Power of 10 |
---|---|---|
I | 1 | 1 × 100 |
X | 10 | 1 × 101 |
C | 100 | 1 × 102 |
M | 1000 | 1 × 103 |
Roman Numeral | Arabic Equivalent | Power of 10 |
---|---|---|
V | 5 | 5 × 100 |
L | 50 | 5 × 101 |
D | 500 | 5 × 102 |
The I, V, X, L, C, D, and M Roman numerals are arrayed in ascending order via a romanDigits Object object
romanDigits[0] = "I";
romanDigits[1] = "V";
romanDigits[2] = "X";
romanDigits[3] = "L";
romanDigits[4] = "C";
romanDigits[5] = "D";
romanDigits[6] = "M";
romanDigits.length = 7;
that instantiates the buildArray( ) constructor.
function buildArray( ) { var a = arguments; for (i = 0; i < a.length; i++) this[i] = a[i]; this.length = a.length; }
var romanDigits = new buildArray("I", "V", "X", "L", "C", "D", "M");
Could romanDigits be formulated as a bona fide Array object? That it could.
var romanDigits = ["I", "V", "X", "L", "C", "D", "M"];
Fragment it
For this and the following sections we will work with a 374 → CCCLXXIV example.
The business end of the RomanNumerals.html script comprises two functions:
(1) an arabicToRoman( ) function effectively divides an inputted Arabic integer into its expanded notation terms;
(2) an arabicToRomanDigit( ) function 'Romanizes' those terms.
A document.write( ) command calls the arabicToRoman( ) function; the 374 argument is given a number identifier.
function arabicToRoman(number) { ... }
document.write("374 in Roman Numerals is ", arabicToRoman(374));
The arabicToRoman( ) body initially declares j, string, and sign variables.
var j, string = "", sign = "";
If number is negative, then sign is set to a stringified minus sign and number is converted to its additive inverse.
if (number < 0) { sign = "-"; number = -number; }
Subsequently number is floor( )ed whether it needs it or not.
number = Math.floor(number);
If number is 4000 or greater, then Out of Range is returned to the calling document.write( ) command and the function exits.
if (number >= 4000) return "Out of Range";
With these preliminaries out of the way, we're ready to get to work on our number=374. A for loop splits 374 into its constituent digits and passes
(a) those digits and
(b) the powers of 10 associated with them
to the arabicToRomanDigit( ) function.
for (j = 0; number > 0; j++) {
digit = number % 10;
number = (number - digit) / 10;
string = arabicToRomanDigit(digit, j) + string; }
The loop runs for three iterations; more generally, it runs as many times as there are digits in number. Noting that
374 = 3 × 102 + 7 × 101 + 4 × 100
here's a rundown of the number processing in those iterations:
j = 0
374 is moduloed by 10 to give 4, which is assigned to a digit variable.
(374 - 4) / 10 gives 37, which is assigned to number.
4 and 0 are passed to the arabicToRomanDigit( ) function.
j = 1
37 is moduloed by 10 to give 7, which is assigned to digit.
(37 - 7) / 10 gives 3, which is assigned to number.
7 and 1 are passed to arabicToRomanDigit( ).
j = 2
3 is moduloed by 10 to give 3, which is assigned to digit.
(3 - 3) / 10 gives 0, which is assigned to number.
3 and 2 are passed to arabicToRomanDigit( ).
With number now 0, the loop stops at the beginning of (what would be) its fourth iteration because the
number > 0
condition returns false.This is not the only way to split number: alternatively, we could stringify number and then go after its characters via, say, the substring( ) method. But given that number is in fact a number (and that we'll be doing some simple arithmetic on the 7 in the arabicToRomanDigit( ) function), the modulo/division approach is the more appropriate way to go.
Romanize it
The arabicToRomanDigit( ) function's digit and j arguments are given the identifiers digit and power, respectively. The arabicToRomanDigit( ) body initially declares i and string variables.
function arabicToRomanDigit(digit, power) {
var i, string = ""; ... }
The declarations are followed by a series of if statements that converts Arabic digits to corresponding Roman strings. Following modern practice, 4s and 9s are handled subtractively, e.g., 4 becomes IV and not IIII. A set of
romanDigits[n + power * 2]
expressions relates the power values to the romanDigits series of numerals.if (digit >= 5 && digit <= 8) { // Adds a V|L|D Roman numeral to string as needed
string = romanDigits[1 + power * 2];
digit = digit - 5; }
if (digit >=1 && digit < 4) { // Adds one, two, or three I|X|C|M Roman numerals to string as needed
for (i = 0; i < digit; i++) string = string + romanDigits[0 + power * 2]; }
if (digit == 4 || digit == 9) {
string = romanDigits[0 + power * 2];
if (digit == 4)
string = string + romanDigits[1 + power * 2];
else
string = string + romanDigits[2 + power * 2]; }
Returning to our 374 → CCCLXXIV example and recalling that
romanDigits = ["I", "V", "X", "L", "C", "D", "M"]
:(4) If digit is 4 and power is 0:
if (digit == 4 || digit == 9) { string = romanDigits[0 + power * 2];
sets string to I.if (digit == 4) string = string + romanDigits[1 + power * 2];
appends a V to the I to give string = IV.(70) If digit is 7 and power is 1:
if (digit >= 5 && digit <= 8) { string = romanDigits[1 + power * 2];
sets string to L.digit = digit - 5;
drops digit to 2.if (digit >=1 && digit < 4) { for (i = 0; i < digit; i++) string = string + romanDigits[0 + power * 2];
appends two Xs to the L to give string = LXX.(300) If digit is 3 and power is 2:
if (digit >=1 && digit < 4) { for (i = 0; i < digit; i++) string = string + romanDigits[0 + power * 2];
sets string to CCC.The arabicToRomanDigit( ) function concludes with a
return string;
statement that returns string to the arabicToRoman( ) function.Assembly and output
Back at the arabicToRoman( ) function, string (i.e., the arabicToRoman( ) string - note that the arabicToRoman( ) string and the arabicToRomanDigit( ) string are both local variables) is appended to the arabicToRomanDigit( ) return and the resulting string is assigned to string.
string = arabicToRomanDigit(digit, j) + string;
For the three
for (j = 0; number > 0; j++)
loop iterations, string goes from an empty string to IV to LXXIV to CCCLXXIV. The order of concatenation operands is important: a string += arabicToRomanDigit(digit, j);
assignment would give a 'reversed' string = IVLXXCCC.When the loop has finished executing, a
return sign + string;
statement appends string to sign and returns the resulting string to the document.write( ) command, which then writes374 in Roman Numerals is CCCLXXIV
to the page. If it were up to me we would send string to an empty
<div id="romanDiv"><div>
via adocument.getElementById("romanDiv").textContent = "374 in Roman numerals is: " + string;
command, but to each his own.
The date
The script presents the following code for
writing a date in Roman numerals:
var month = new buildArray("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December");
var today = new Date( );
var yr = today.getYear( );
if (yr < 100) yr = yr + 1900;
document.write("<br>Today is ", arabicToRoman(today.getDate( )), " ", month[today.getMonth( )], " ", arabicToRoman(yr));
I see you rolling your eyes. "getYear( )??" Yeah, that's the reason why the year in the date demo at the javascript/RomanNumerals.html page is off. Setting yr to
today.getFullYear( )
sorts out the situation.Sample output:
Today is VII September MMXIV
Demo
As promised, we end with a demo.
Input an Arabic integer in the range 1-3999 inclusive:
A
!/^\d+$/.test(number)
gate weeds out negative numbers, floating-point numbers, and nonnumeric inputs.Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)