Roman Numeral Conversion in Both Directions

Convert integers from 1 to 3999 into Roman numerals and parse valid numerals back to numbers with subtraction and validation.

JavascriptData Integrity

Code

snippet
const NUMERALS = [
  [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(value) {
  if (!Number.isInteger(value) || value < 1 || value > 3999) {
    throw new RangeError("value must be an integer between 1 and 3999");
  }
  let remaining = value;
  let output = "";
  for (const [amount, symbol] of NUMERALS) {
    while (remaining >= amount) {
      output += symbol;
      remaining -= amount;
    }
  }
  return output;
}

export function fromRoman(roman) {
  if (typeof roman !== "string" || !/^[MDCLXVI]+$/.test(roman)) {
    throw new RangeError("roman must contain only MDCLXVI");
  }
  const values = { M: 1000, D: 500, C: 100, L: 50, X: 10, V: 5, I: 1 };
  let total = 0;
  for (let index = 0; index < roman.length; index += 1) {
    const current = values[roman[index]];
    const next = values[roman[index + 1]] ?? 0;
    total += current < next ? -current : current;
  }
  if (total < 1 || total > 3999 || toRoman(total) !== roman) {
    throw new RangeError("invalid or non-canonical roman numeral");
  }
  return total;
}