diff --git a/README.md b/README.md index bddf747..6b18a2a 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,7 @@ const count = stringzy.analyze.wordCount('Hello world'); // 2 - [formatPhone](#formatphone) - Formats a phone number string to standard format - [formatDuration](#formatduration) - Converts a duration in seconds or milliseconds into a human-readable string - [trim](#trim) - Removes unnecessary whitespace from a string. +- [formatRomanNumeral](#formatromannumeral) - Converts a positive integer into its Roman numeral representation. ## 📋 API Reference @@ -1302,6 +1303,28 @@ trim('line \n breaks\tand tabs'); // 'line breaks and tabs' | --------- | ------ | -------- | ------------------------------------ | | str | string | required | The input string to trim and normalize.| +#### formatRomanNumeral(num) +Converts a positive integer into its Roman numeral representation (supports values from 1 to 3999). +Throws an error for invalid, non-numeric, zero, or negative inputs. + +```javascript +import { formatRomanNumeral } from 'stringzy'; + +formatRomanNumeral(1); // "I" +formatRomanNumeral(4); // "IV" +formatRomanNumeral(9); // "IX" +formatRomanNumeral(58); // "LVIII" +formatRomanNumeral(1994); // "MCMXCIV" + +// Invalid cases +formatRomanNumeral(0); // RangeError +formatRomanNumeral(-5); // RangeError +formatRomanNumeral('123'); // TypeError +``` +| Parameter | Type | Default | Description | +| --------- | ------ | -------- | ---------------------------------------------------- | +| num | number | required | The integer (1–3999) to convert into Roman numerals. | + ## 🔧 Usage Patterns ### Individual Function Imports diff --git a/src/formatting/index.ts b/src/formatting/index.ts index 65e4acb..a6ca91e 100644 --- a/src/formatting/index.ts +++ b/src/formatting/index.ts @@ -3,17 +3,20 @@ export { formatNumber } from './number'; export { formatPhone } from './phone'; export { formatDuration } from './duration'; export { trim } from './trim'; +export { formatRomanNumeral } from './romanNumerals'; import { capitalize } from './capitalize'; import { formatNumber } from './number'; import { formatPhone } from './phone'; import { formatDuration } from './duration'; import { trim } from './trim'; +import { formatRomanNumeral } from './romanNumerals'; export const formatting = { capitalize, formatNumber, formatPhone, formatDuration, - trim + trim, + formatRomanNumeral }; diff --git a/src/formatting/romanNumerals.ts b/src/formatting/romanNumerals.ts new file mode 100644 index 0000000..1fdff3e --- /dev/null +++ b/src/formatting/romanNumerals.ts @@ -0,0 +1,57 @@ +/** + * Converts a positive integer into its Roman numeral representation. + * + * Supports numbers from 1 to 3999 using standard Roman numeral notation. + * + * Examples: + * 1 → "I" + * 4 → "IV" + * 9 → "IX" + * 58 → "LVIII" + * 1994 → "MCMXCIV" + * 2025 → "MMXXV" + * 3999 → "MMMCMXCIX" + * + * @param {number} num - The positive integer to convert (1–3999). + * @returns {string} The Roman numeral representation of the given number. + * @throws {RangeError} If the number is less than or equal to 0, or greater than 3999. + * @throws {TypeError} If the input is not a number. + */ +export function formatRomanNumeral(num: number): string { + if (typeof num !== 'number' || Number.isNaN(num)) { + throw new TypeError('Input must be a number'); + } + + if (num <= 0) { + throw new RangeError('Roman numerals are only defined for positive integers'); + } + + if (num > 3999) { + throw new RangeError('Roman numerals are supported only up to 3999'); + } + + const values = [ + 1000, 900, 500, 400, + 100, 90, 50, 40, + 10, 9, 5, 4, 1 + ]; + + const symbols = [ + 'M', 'CM', 'D', 'CD', + 'C', 'XC', 'L', 'XL', + 'X', 'IX', 'V', 'IV', 'I' + ]; + + let result = ''; + let i = 0; + + while (num > 0) { + while (num >= values[i]) { + result += symbols[i]; + num -= values[i]; + } + i++; + } + + return result; +} diff --git a/src/tests/formatting/romanNumerals.test.ts b/src/tests/formatting/romanNumerals.test.ts new file mode 100644 index 0000000..c2decff --- /dev/null +++ b/src/tests/formatting/romanNumerals.test.ts @@ -0,0 +1,36 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert'; +import { formatRomanNumeral } from '../../formatting/romanNumerals'; + +describe('formatRomanNumeral', () => { + it('correctly converts basic numerals', () => { + assert.strictEqual(formatRomanNumeral(1), 'I'); + assert.strictEqual(formatRomanNumeral(4), 'IV'); + assert.strictEqual(formatRomanNumeral(9), 'IX'); + }); + + it('correctly converts mid-range numbers', () => { + assert.strictEqual(formatRomanNumeral(58), 'LVIII'); // 50 + 5 + 3 + assert.strictEqual(formatRomanNumeral(1994), 'MCMXCIV'); // 1000 + 900 + 90 + 4 + assert.strictEqual(formatRomanNumeral(2025), 'MMXXV'); + }); + + it('correctly converts upper limit', () => { + assert.strictEqual(formatRomanNumeral(3999), 'MMMCMXCIX'); + }); + + it('throws error for 0 and negative numbers', () => { + assert.throws(() => formatRomanNumeral(0), /Roman numerals are only defined for positive integers/); + assert.throws(() => formatRomanNumeral(-5), /Roman numerals are only defined for positive integers/); + }); + + it('throws error for numbers above 3999', () => { + assert.throws(() => formatRomanNumeral(4000), /Roman numerals are supported only up to 3999/); + }); + + it('throws TypeError for non-number inputs', () => { + assert.throws(() => formatRomanNumeral('123' as any), /Input must be a number/); + assert.throws(() => formatRomanNumeral(null as any), /Input must be a number/); + assert.throws(() => formatRomanNumeral(undefined as any), /Input must be a number/); + }); +});