Roman Numeral Converter CLI
A Node CLI that converts integers to Roman numerals and back, rejecting out-of-range and malformed input with clear errors and tests.
Project
node-roman-numeral-cli
Entry point: roman.mjs
Install
node --versionRun
node roman.mjs 1994Test
node --test roman.test.mjsExpected output
Prints the Roman numeral for the given integer, for example MCMXCIV for 1994. Passing -r converts in reverse.Dependencies
- node >=20
What it does
Greedily maps an integer onto the standard subtractive notation and validates the 1 to 3999 range. With -r it parses a numeral and rejects malformed strings by checking each token.
const MAP = [
[1000, 'M'], [900, 'CM'], [500, 'D'], [400, 'CD'], [100, 'C'], [90, 'XC'],
[50, 'L'], [40, 'XL'], [10, 'X'], [9, 'IX'], [5, 'V'], [4, 'IV'], [1, 'I'],
];
export function toRoman(n) {
if (!Number.isInteger(n) || n < 1 || n > 3999) throw new RangeError('out of range 1..3999');
let out = '';
for (const [value, sym] of MAP) while (n >= value) { out += sym; n -= value; }
return out;
}
export function fromRoman(s) {
if (!/^(M{0,3})(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$/.test(s)) throw new SyntaxError('invalid numeral');
let total = 0;
for (let i = 0; i < s.length; i++) {
const cur = MAP.find(([, sym]) => sym.length === 1 && sym === s[i])?.[0] ?? 0;
const nxt = i + 1 < s.length ? (MAP.find(([, sym]) => sym.length === 1 && sym === s[i + 1])?.[0] ?? 0) : 0;
total += cur < nxt ? -cur : cur;
}
return total;
}
const args = process.argv.slice(2);
if (args[0] === '-r') console.log(fromRoman(args[1]));
else console.log(toRoman(Number(args[0])));
Run and verify
node roman.mjs 1994
Run the test command to check the implementation: node --test roman.test.mjs.
Expected Output
1994 in standard form is MCMXCIV (1000 + 900 + 90 + 4). The default direction converts an integer to a numeral:
MCMXCIV
Limitations
Supports the standard 1 to 3999 range only, because classical notation has no zero or negative form and no dedicated symbols above 3999.