Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -1302,6 +1303,28 @@ trim('line \n breaks\tand tabs'); // 'line breaks and tabs'
| --------- | ------ | -------- | ------------------------------------ |
| str | string | required | The input string to trim and normalize.|

#### <a id="formatromannumeral"></a>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
Expand Down
5 changes: 4 additions & 1 deletion src/formatting/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
57 changes: 57 additions & 0 deletions src/formatting/romanNumerals.ts
Original file line number Diff line number Diff line change
@@ -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;
}
36 changes: 36 additions & 0 deletions src/tests/formatting/romanNumerals.test.ts
Original file line number Diff line number Diff line change
@@ -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/);
});
});