TUSHAR.
Article14 min read

Introducing @tushar_rayamajhi/roman_converter — The Most Complete Roman Numeral Library for JavaScript

A deep dive into every feature of the roman_converter npm package — core conversion, arithmetic, extended numerals up to 3,999,999, classical Latin words, base-12 Roman fractions, a live clock, React hooks, and a full CLI. Zero dependencies, full TypeScript support.

What Is It?

@tushar_rayamajhi/roman_converter is a zero-dependency JavaScript/TypeScript library that does far more than the typical "convert a number to Roman numerals" package. It ships with eight distinct feature categories, three of which you will not find anywhere else on npm.

GitHub: tusharrayamajhi/roman-to-decimal
Live demo: roman_converter package page


Installation

npm install @tushar_rayamajhi/roman_converter
# or
yarn add @tushar_rayamajhi/roman_converter
# or
pnpm add @tushar_rayamajhi/roman_converter

Requires Node.js ≥ 18. Zero external dependencies. Full TypeScript definitions included — no @types/ package needed.


1. Core Conversion (1–3,999)

The foundation of the library:

import { toRoman, fromRoman, isValidRoman, isValidNumber } from '@tushar_rayamajhi/roman_converter'

toRoman(2024)                     // "MMXXIV"
toRoman(42)                       // "XLII"
toRoman(42, { lowercase: true })  // "xlii"

fromRoman('MCMXCIV')   // 1994
fromRoman('xlii')      // 42  (case-insensitive input)

isValidRoman('XLII')   // true
isValidRoman('IIII')   // false  (invalid form — strict validation)
isValidNumber(3999)    // true
isValidNumber(4000)    // false

A key difference from naive implementations: fromRoman validates strict Roman numeral form, so IIII (four I's) correctly returns false — only IV is accepted.


2. Arithmetic Operations

All four arithmetic operations accept Roman numeral strings or integers interchangeably. You can freely mix them:

import { add, subtract, multiply, divide } from '@tushar_rayamajhi/roman_converter'

add('XIV', 'III')      // "XVII"  (14 + 3)
add(10, 'V')           // "XV"    (mixed input)
subtract('X', 'IV')    // "VI"    (10 - 4)
multiply('V', 'III')   // "XV"    (5 × 3)
divide('X', 'III')     // "III"   (10 ÷ 3, floor division)

Results are always returned as Roman numeral strings. A RangeError is thrown if the result falls outside 1–3,999.

All functions also accept an options object: { lowercase: true } to return lowercase output.


3. Utilities

import { range, sort, compare, breakdown, batchToRoman, batchFromRoman } from '@tushar_rayamajhi/roman_converter'

// Generate a range of Roman numerals
range(1, 5)                    // ["I", "II", "III", "IV", "V"]
range(10, 1, -3)               // ["X", "VII", "IV", "I"]  (negative step)

// Sort mixed arrays (preserves input type)
sort(['X', 'V', 'I', 'M'])    // ["I", "V", "X", "M"]
sort([10, 'V', 1, 'M'], 'desc') // ["M", 10, "V", 1]

// Compare two values
compare('X', 'V')              // 1  (X > V)
compare('V', 'V')              // 0  (equal)
compare('I', 'V')              // -1 (I < V)

// Decompose a Roman numeral into its additive parts
breakdown('MCMXCIX')
// [
//   { numeral: "M",  value: 1000 },
//   { numeral: "CM", value:  900 },
//   { numeral: "XC", value:   90 },
//   { numeral: "IX", value:    9 }
// ]

// Batch conversion
batchToRoman([1, 5, 10, 50, 100, 500, 1000])
// ["I", "V", "X", "L", "C", "D", "M"]

batchFromRoman(["I", "V", "X"])
// [1, 5, 10]

sort preserves the input type: if you pass strings, you get strings back; integers stay integers. Both Roman strings and integers can be in the same array.


4. Extended Numerals — Unique Feature #1

Range: 1–3,999,999

Standard Roman numerals top out at 3,999 (MMMCMXCIX). This library extends the system using parenthetical vinculum notation — a line over a numeral historically multiplied it by 1,000. The parenthetical form is the ASCII-safe equivalent:

SymbolValue
(V)5,000
(X)10,000
(L)50,000
(C)100,000
(D)500,000
(M)1,000,000
import { toExtendedRoman, fromExtendedRoman, isValidExtendedNumber } from '@tushar_rayamajhi/roman_converter'

toExtendedRoman(4000)     // "(IV)"
toExtendedRoman(1000000)  // "(M)"
toExtendedRoman(1999999)  // "(M)(CM)(XC)(IX)CMXCIX"

fromExtendedRoman('(M)')         // 1000000
fromExtendedRoman('(IV)')        // 4000
isValidExtendedNumber(3999999)   // true
isValidExtendedNumber(4000000)   // false

No other npm package handles extended Roman numerals beyond 3,999.


5. Roman Clock / Time

Full time conversion with multiple formatting options:

import { toRomanTime, fromRomanTime, nowInRoman } from '@tushar_rayamajhi/roman_converter'

// 24-hour format (default)
toRomanTime('14:30')                           // "XIV:XXX"

// 12-hour format
toRomanTime('14:30', { format: '12h' })        // "II:XXX"

// Include seconds
toRomanTime('14:30:45', { seconds: true })     // "XIV:XXX:XLV"

// Meridiem (AM/PM)
toRomanTime('14:30', { meridiem: true })       // "XIV:XXX PM"

// Midnight — zero minutes shown as · (middle dot, classical "nulla")
toRomanTime('00:00')                           // "XII:·"

// Parse a Roman time string back
fromRomanTime('XIV:XXX')
// { hours: 14, minutes: 30, seconds: 0, formatted: '14:30' }

// Current system time
nowInRoman()                                   // e.g. "XIV:XXX"
nowInRoman({ format: '12h', seconds: true })   // e.g. "II:XXX:XLV"

The zero-minutes representation using the middle dot (·) is historically accurate — classical Latin used "nulla" for zero.


6. Classical Latin Words — Unique Feature #2

Range: 1–3,999

import { toWords, fromWords } from '@tushar_rayamajhi/roman_converter'

toWords(1)     // "unus"
toWords(4)     // "quattuor"
toWords(18)    // "duodeviginti"   ("two from twenty" — subtraction form)
toWords(19)    // "undeviginti"    ("one from twenty")
toWords(42)    // "quadraginta duo"
toWords(100)   // "centum"
toWords(1000)  // "mille"
toWords(2000)  // "duo milia"      (neuter nominative plural)
toWords(3000)  // "tria milia"
toWords(3999)  // "tria milia nongenti nonaginta novem"

// Reverse: Latin words → integer
fromWords('quadraginta duo')  // 42
fromWords('duo milia')        // 2000

The library handles the nuances of classical Latin correctly:

  • Subtraction forms for x8 and x9 (e.g., 18 = duodeviginti, not decem octo)
  • Correct thousand plurals — "mille" (1000), "duo milia" (2000), "tria milia" (3000)
  • Neuter nominative for numbers preceding milia

7. Roman Fractions (Uncia) — Unique Feature #3

Ancient Roman commerce used a base-12 fraction system with the as as the unit. Each twelfth was called an uncia — the origin of our words "ounce" and "inch":

SymbolNameTwelfthsDecimal
·uncia1/120.0833
··sextans2/120.1667
···quadrans3/120.25
····triens4/120.3333
·····quincunx5/120.4167
Ssemis6/120.5
septunx7/120.5833
S··bes8/120.6667
S···dodrans9/120.75
S····dextans10/120.8333
S·····deunx11/120.9167
ASas12/121.0
import { toUncia, fromUncia, unciaInfo, isValidUncia, listUncia } from '@tushar_rayamajhi/roman_converter'

// Convert decimal to uncia symbol
toUncia(0.5)     // "S"     (semis — half)
toUncia(0.25)    // "···"   (quadrans — quarter)
toUncia(2/3)     // "S··"   (bes — eight twelfths)
toUncia(1/12)    // "·"     (uncia — one twelfth)
toUncia(1)       // "AS"    (as — whole unit)

// Convert symbol to decimal
fromUncia('S')   // 0.5
fromUncia('···') // 0.25

// Get full information for a symbol
unciaInfo('S')
// { twelfths: 6, symbol: 'S', name: 'semis', decimal: 0.5 }

// List all 12 uncia entries
listUncia()
// [{ twelfths: 12, symbol: 'AS', name: 'as', decimal: 1 }, ...]

The toUncia function rounds the input to the nearest twelfth — so 0.3 becomes ··· (quadrans = 0.25, the closest).


8. React Hooks

import { useRoman, useRomanClock } from '@tushar_rayamajhi/roman_converter/react'

// Synchronized integer ↔ Roman state
function RomanCounter() {
  const { roman, integer, increment, decrement, set, reset } = useRoman(1)
  
  return (
    <div>
      <p>{roman} ({integer})</p>  {/* "I" (1) → "II" (2) → … */}
      <button onClick={() => increment()}>+1</button>
      <button onClick={() => increment(5)}>+5</button>
      <button onClick={() => decrement()}>−1</button>
      <button onClick={() => set('X')}>Set to X</button>
      <button onClick={reset}>Reset</button>
    </div>
  )
}

// Live Roman numeral clock — updates every second
function RomanClock() {
  const { time, hours, minutes, seconds } = useRomanClock({
    format: '24h',
    seconds: true,
  })
  return <span className="font-mono">{time}</span>
  // e.g. "XIV:XXX:XLV"
}

useRoman exposes:

  • roman — current Roman numeral string
  • integer — current integer
  • isValid — whether integer is in range 1–3,999
  • set(value) — accepts integer or Roman string
  • increment(step?) / decrement(step?) — clamped to 1–3,999
  • reset() — returns to initial value

useRomanClock accepts the same options as toRomanTime() and returns { time, hours, minutes, seconds }.


9. CLI

Install globally or use npx:

# Convert
roman 2024            # MMXXIV
roman XLII            # 42

# Arithmetic
roman add XIV III     # XVII
roman sub X IV        # VI
roman mul V III       # XV
roman div X III       # III

# Utilities
roman range 1 5            # I, II, III, IV, V
roman sort X V I M         # I, V, X, M
roman breakdown MCMXCIX    # M=1000, CM=900, XC=90, IX=9

# Extended numerals
roman ext 1000000          # (M)
roman ext "(M)"            # 1000000

# Time
roman time                 # current time
roman time 14:30           # XIV:XXX
roman time from XIV:XXX    # 14:30

# Latin words
roman words 42             # quadraginta duo
roman words from "duo milia"  # 2000

# Fractions
roman uncia 0.5            # S  (semis)
roman uncia from S         # 0.5
roman uncia list           # all 12 entries

Flags: --lowercase / -l, --json, --12h, --seconds, --version, --help.


TypeScript Support

Full type definitions ship with the package — no @types/ installation needed:

import type {
  ToRomanOptions,
  RomanTimeOptions,
  ParsedTime,
  BreakdownPart,
  UnciaEntry,
  TimeObject,
} from '@tushar_rayamajhi/roman_converter'

const opts: ToRomanOptions = { lowercase: true }
const timeOpts: RomanTimeOptions = { format: '12h', seconds: true, meridiem: true }
const parts: BreakdownPart[] = breakdown('MCMXCIV')
// Each part: { numeral: string, value: number }

const info: UnciaEntry = unciaInfo('S')
// { twelfths: number, symbol: string, name: string, decimal: number }

Try It Yourself

The package is live on npm and the source is on GitHub. Visit the interactive package page for a playground where you can try every feature directly in the browser — no installation needed.

npm install @tushar_rayamajhi/roman_converter
Tushar Rayamajhi | AI Engineer & Backend Developer