4 * Integer to Unicode character.
5 * These pre-combined numbers are only used for direct digit mappings
7 * A previous implementation would compact occurrences of these multi-character
8 * combinations in larger numbers; however, some fonts render the multi-character
9 * combinations as slightly different sizes than the stand-alone characters,
10 * which made them render oddly.
12 const singleMapping
= {
13 1: '\u2160', // Ⅰ Ⅰ
14 2: '\u2161', // Ⅱ Ⅱ
15 3: '\u2162', // Ⅲ Ⅲ
16 4: '\u2163', // Ⅳ Ⅳ
17 5: '\u2164', // Ⅴ Ⅴ
18 6: '\u2165', // Ⅵ Ⅵ
19 7: '\u2166', // Ⅶ Ⅶ
20 8: '\u2167', // Ⅷ Ⅷ
21 9: '\u2168', // Ⅸ Ⅸ
22 10: '\u2169', // Ⅹ Ⅹ
23 11: '\u216a', // Ⅺ Ⅺ
24 12: '\u216b', // Ⅻ Ⅻ
28 * Viable mappings from a number to the Roman representation.
31 1: '\u2160', // Ⅰ Ⅰ
32 4: '\u2160\u2164', // ⅠⅤ ⅠⅤ
33 5: '\u2164', // Ⅴ Ⅴ
34 9: '\u2160\u2169', // Ⅸ Ⅸ
35 10: '\u2169', // Ⅹ Ⅹ
36 40: '\u2169\u216c', // ⅩⅬ ⅩⅬ
37 50: '\u216c', // Ⅼ Ⅼ
38 90: '\u2169\u216d', // ⅩⅭ ⅩⅭ
39 100: '\u216d', // Ⅽ Ⅽ
40 400: '\u216d\u216e', // ⅭⅮ ⅭⅮ
41 500: '\u216e', // Ⅾ Ⅾ
42 900: '\u216d\u216f', // ⅭⅯ ⅭⅯ
43 1000: '\u216f', // Ⅿ Ⅿ
47 * Render all characters of #s as HTML-encoded entities.
51 function htmlEncode(s
) {
54 .map((c
) => `&#${c.codePointAt(0)};`)
59 * The keys of our number->roman mappings, sorted highest-first.
60 * We will walk this list, rendering input-value-divided-
61 * by-the-key-number mapped values, modulus-ing the input-value, and
62 * repeating for the remaining key-numbers.
64 const divisors
= Object
.keys(mapping
)
65 .sort((a
, b
) => b
- a
)
69 * Convert a number to its Roman representation, using Unicode characters
72 * @param {Boolean} asEntities if true, return html instead of unicode
74 * @throws {RangeError}
76 function toRoman(num
, asEntities
= false) {
77 if (!num
|| isNaN(Number(num
))) {
78 throw new RangeError(`cannot convert '${num}' (${typeof num})`);
81 throw new RangeError('cannot convert negative numbers');
83 const romanDigits
= [];
85 romanDigits
.push(singleMapping
[num
]);
87 divisors
.forEach((d
) => {
88 const length
= Math
.floor(num
/ d
);
89 romanDigits
.push(mapping
[d
].repeat(length
));
93 const romanString
= romanDigits
.join('');
95 return asEntities
? htmlEncode(romanString
) : romanString
;