diff --git a/src/tools/index.ts b/src/tools/index.ts
index d4e49691..782478a7 100644
--- a/src/tools/index.ts
+++ b/src/tools/index.ts
@@ -4,6 +4,7 @@ import type { ToolCategory } from './Tool';
import { tool as tokenGenerator } from './token-generator';
import { tool as hashText } from './hash-text';
import { tool as uuidGenerator } from './uuid-generator';
+import { tool as romanNumeralConverter } from './roman-numeral-converter';
export const toolsByCategory: ToolCategory[] = [
{
@@ -11,6 +12,11 @@ export const toolsByCategory: ToolCategory[] = [
icon: LockOpen,
components: [tokenGenerator, hashText, uuidGenerator],
},
+ {
+ name: 'Converter',
+ icon: LockOpen,
+ components: [romanNumeralConverter],
+ },
];
export const tools = toolsByCategory.flatMap(({ components }) => components);
diff --git a/src/tools/roman-numeral-converter/index.ts b/src/tools/roman-numeral-converter/index.ts
new file mode 100644
index 00000000..ecaf57e4
--- /dev/null
+++ b/src/tools/roman-numeral-converter/index.ts
@@ -0,0 +1,11 @@
+import { LetterX } from '@vicons/tabler';
+import type { ITool } from '../Tool';
+
+export const tool: ITool = {
+ name: 'Roman numeral converter',
+ path: '/roman-numeral-converter',
+ description: 'Convert Roman numerals to numbers and convert numbers to Roman numerals.',
+ keywords: ['roman', 'arabic', 'converter', 'X', 'I', 'V', 'L', 'C', 'D', 'M'],
+ component: () => import('./roman-numeral-converter.vue'),
+ icon: LetterX,
+};
diff --git a/src/tools/roman-numeral-converter/roman-numeral-converter.service.test.ts b/src/tools/roman-numeral-converter/roman-numeral-converter.service.test.ts
new file mode 100644
index 00000000..21a747cf
--- /dev/null
+++ b/src/tools/roman-numeral-converter/roman-numeral-converter.service.test.ts
@@ -0,0 +1,73 @@
+import { expect, describe, it } from 'vitest';
+import { arabicToRoman } from './roman-numeral-converter.service';
+
+describe('roman-numeral-converter', () => {
+ describe('arabicToRoman', () => {
+ it('should convert numbers lower than 1 to empty string', () => {
+ expect(arabicToRoman(-100)).toEqual('');
+ expect(arabicToRoman(-42)).toEqual('');
+ expect(arabicToRoman(-26)).toEqual('');
+ expect(arabicToRoman(-10)).toEqual('');
+ expect(arabicToRoman(0)).toEqual('');
+ expect(arabicToRoman(0.5)).toEqual('');
+ expect(arabicToRoman(0.9)).toEqual('');
+ });
+
+ it('should convert floating points number to the lower integer in roman version', () => {
+ expect(arabicToRoman(-100)).toEqual('');
+ expect(arabicToRoman(-42)).toEqual('');
+ expect(arabicToRoman(-26)).toEqual('');
+ expect(arabicToRoman(-10)).toEqual('');
+ expect(arabicToRoman(0)).toEqual('');
+ expect(arabicToRoman(0.5)).toEqual('');
+ expect(arabicToRoman(0.9)).toEqual('');
+ });
+
+ it('should convert positive integers to roman numbers', () => {
+ expect(arabicToRoman(1)).toEqual('I');
+ expect(arabicToRoman(2)).toEqual('II');
+ expect(arabicToRoman(3)).toEqual('III');
+ expect(arabicToRoman(4)).toEqual('IV');
+ expect(arabicToRoman(5)).toEqual('V');
+ expect(arabicToRoman(6)).toEqual('VI');
+ expect(arabicToRoman(7)).toEqual('VII');
+ expect(arabicToRoman(8)).toEqual('VIII');
+ expect(arabicToRoman(9)).toEqual('IX');
+ expect(arabicToRoman(10)).toEqual('X');
+ expect(arabicToRoman(11)).toEqual('XI');
+ expect(arabicToRoman(12)).toEqual('XII');
+ expect(arabicToRoman(13)).toEqual('XIII');
+ expect(arabicToRoman(14)).toEqual('XIV');
+ expect(arabicToRoman(15)).toEqual('XV');
+ expect(arabicToRoman(16)).toEqual('XVI');
+ expect(arabicToRoman(17)).toEqual('XVII');
+ expect(arabicToRoman(18)).toEqual('XVIII');
+ expect(arabicToRoman(19)).toEqual('XIX');
+ expect(arabicToRoman(20)).toEqual('XX');
+ expect(arabicToRoman(21)).toEqual('XXI');
+ expect(arabicToRoman(24)).toEqual('XXIV');
+ expect(arabicToRoman(28)).toEqual('XXVIII');
+ expect(arabicToRoman(29)).toEqual('XXIX');
+ expect(arabicToRoman(30)).toEqual('XXX');
+ expect(arabicToRoman(40)).toEqual('XL');
+ expect(arabicToRoman(50)).toEqual('L');
+ expect(arabicToRoman(60)).toEqual('LX');
+ expect(arabicToRoman(70)).toEqual('LXX');
+ expect(arabicToRoman(80)).toEqual('LXXX');
+ expect(arabicToRoman(90)).toEqual('XC');
+ expect(arabicToRoman(100)).toEqual('C');
+ expect(arabicToRoman(200)).toEqual('CC');
+ expect(arabicToRoman(300)).toEqual('CCC');
+ expect(arabicToRoman(400)).toEqual('CD');
+ expect(arabicToRoman(500)).toEqual('D');
+ expect(arabicToRoman(600)).toEqual('DC');
+ expect(arabicToRoman(700)).toEqual('DCC');
+ expect(arabicToRoman(800)).toEqual('DCCC');
+ expect(arabicToRoman(900)).toEqual('CM');
+ expect(arabicToRoman(999)).toEqual('CMXCIX');
+ expect(arabicToRoman(1000)).toEqual('M');
+ expect(arabicToRoman(2000)).toEqual('MM');
+ expect(arabicToRoman(9000)).toEqual('MMMMMMMMM');
+ });
+ });
+});
diff --git a/src/tools/roman-numeral-converter/roman-numeral-converter.service.ts b/src/tools/roman-numeral-converter/roman-numeral-converter.service.ts
new file mode 100644
index 00000000..581359f6
--- /dev/null
+++ b/src/tools/roman-numeral-converter/roman-numeral-converter.service.ts
@@ -0,0 +1,18 @@
+export function arabicToRoman(num: number) {
+ if (num < 1) return '';
+
+ const lookup: { [key: string]: number } = { M: 1000, CM: 900, D: 500, CD: 400, C: 100, XC: 90, L: 50, XL: 40, X: 10, IX: 9, V: 5, IV: 4, I: 1 };
+ let roman = '';
+ for (const i in lookup) {
+ while (num >= lookup[i]) {
+ roman += i;
+ num -= lookup[i];
+ }
+ }
+ return roman;
+}
+
+export function romanToArabic(s: string) {
+ const map: { [key: string]: number } = { I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000 };
+ return [...s].reduce((r, c, i, s) => (map[s[i + 1]] > map[c] ? r - map[c] : r + map[c]), 0);
+}
diff --git a/src/tools/roman-numeral-converter/roman-numeral-converter.vue b/src/tools/roman-numeral-converter/roman-numeral-converter.vue
new file mode 100644
index 00000000..4b7a1a2c
--- /dev/null
+++ b/src/tools/roman-numeral-converter/roman-numeral-converter.vue
@@ -0,0 +1,46 @@
+
+
+
+
+
+ {{ outputRoman }}
+ Copy
+
+
+
+
+
+
+ {{ outputNumeral }}
+ Copy
+
+
+
+
+
+
+
+
\ No newline at end of file