"use strict"; const VALUE_SYMBOLS = [ [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"] ]; function toRoman(num) { let n = num; let out = ""; for (const [value, symbol] of VALUE_SYMBOLS) { while (n >= value) { out += symbol; n -= value; } } return out; } function toArabic(roman) { const map = { I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000 }; const s = roman.toUpperCase(); let total = 0; for (let i = 0; i < s.length; i++) { const cur = map[s[i]]; if (cur === undefined) throw new Error(`invalid roman numeral character: ${s[i]}`); const next = map[s[i + 1]]; if (next !== undefined && cur < next) { total -= cur; } else { total += cur; } } return total; } module.exports = { slug: "roman-numerals-convert", title: "Roman numeral conversion", deck_category: "other", description: "Converts between Arabic numbers (1-3999) and Roman numerals using standard subtractive notation.", inputs: [ { name: "direction", type: "string", required: true, description: "Which way to convert.", enum: ["to_roman", "to_arabic"] }, { name: "value", type: "string", required: true, description: "For to_roman: an integer 1-3999 as a string. For to_arabic: a Roman numeral string." } ], outputs: [ { name: "result", description: "The converted value: a Roman numeral string, or an Arabic integer." } ], steps: [ "Standard subtractive notation: symbols I=1, V=5, X=10, L=50, C=100, D=500, M=1000; a smaller symbol placed immediately before a larger one is subtracted (IV=4, IX=9, XL=40, XC=90, CD=400, CM=900); otherwise symbols are added left to right.", "To convert to Roman: repeatedly take the largest value-symbol pair (from 1000 down to 1, including the six subtractive pairs CM, CD, XC, XL, IX, IV) that fits into the remaining number, appending its symbol and subtracting its value, until the remaining number is 0.", "To convert to Arabic: scan the numeral left to right, adding each symbol's value, except when a symbol's value is less than the value of the symbol immediately following it, in which case it is subtracted instead.", "Valid range for to_roman is 1 to 3999 (the standard scheme has no symbol for zero or for 4000 and above without overline notation)." ], constants: [], sources: [ { kind: "edition_page", value: "Cajori, Florian. A History of Mathematical Notations. Open Court, 1928.", title: "A History of Mathematical Notations (Roman numeral subtractive notation)", publisher: "Open Court", retrieved_utc: "2026-09-30T19:59:00Z", note: "Standard reference for the additive/subtractive rules of Roman numeral notation." } ], disclaimer: null, questionText(inputs) { if (inputs.direction === "to_roman") return `What is ${inputs.value} written as a Roman numeral?`; return `What is the Roman numeral ${inputs.value} written as an Arabic number?`; }, run(inputs) { const { direction, value } = inputs || {}; if (direction !== "to_roman" && direction !== "to_arabic") throw new Error("direction must be to_roman or to_arabic"); if (typeof value !== "string" || value.length === 0) throw new Error("value must be a non-empty string"); if (direction === "to_roman") { const n = Number(value); if (!Number.isInteger(n) || n < 1 || n > 3999) throw new Error("value must be an integer between 1 and 3999 for to_roman"); const roman = toRoman(n); return { answer: roman, values: { result: roman } }; } const n = toArabic(value); if (n < 1 || n > 3999) throw new Error("converted value out of the standard 1-3999 range"); return { answer: `${n}`, values: { result: n } }; }, examples: [ { name: "worked example", inputs: { direction: "to_roman", value: "1994" }, expected: { answer: "MCMXCIV" }, check: "1994 = 1000(M) + 900(CM) + 90(XC) + 4(IV) = MCMXCIV, the well known textbook example commonly used to illustrate subtractive notation." }, { name: "test vector: to Arabic", inputs: { direction: "to_arabic", value: "MMXXVI" }, expected: { answer: "2026" }, check: "MMXXVI = M(1000)+M(1000)+X(10)+X(10)+V(5)+I(1) = 2000+20+5+1 = 2026, verified by direct summation since no symbol here precedes a larger one." } ] };