Roman numeral converter
Switch between ordinary numbers and Roman numerals in both directions: 2026 becomes MMXXVI, and MMXXVI reads back as 2026. Non-standard spellings are rejected instead of guessed, so dates and section numbers stay right. Free in your browser.
- Free
- Runs in your browser
- No sign-up
- Your data stays on your device
Run — free
Worked example
- Value
- 1
- Calculation mode
- To roman
- Result
- I
- Input value
- 1
free in your browser
Seven letters and six subtractive pairs
Roman numerals use seven letters: I for 1, V for 5, X for 10, L for 50, C for 100, D for 500 and M for 1000. Six pairs put a smaller letter before a larger one to subtract it: IV is 4, IX is 9, XL is 40, XC is 90, CD is 400 and CM is 900. To write a number, the converter takes the largest of these thirteen values that still fits, writes its symbol, subtracts it and repeats until nothing is left. That is how 2026 becomes MMXXVI, and why the result is always the standard shortest form.
Where Roman numerals still appear
They mark publication and film years in credits, on plates and on title pages, and they number sections and clauses as I, II, III in outlines and contracts. Reading them back into ordinary numbers helps sort old references. There is no numeral for zero, and the largest value the classic symbols can write is 3999, MMMCMXCIX. When reading, any spelling that is not the standard form, such as IIII or IC, is rejected rather than guessed, so a typing slip never silently turns into a wrong number.
What you can do with it
Copyright and edition years
Render publication or film years as Roman numerals (e.g. 2026 to MMXXVI) for credits, plates and title pages.
Outline and clause numbering
Generate I, II, III style section markers, or parse legacy Roman-numbered references back to integers for sorting.
FAQ
What range of numbers is supported?
Integers 1 to 3999 inclusive. There is no numeral for zero and 3999 (MMMCMXCIX) is the largest classic value.
Why is 4 written as IV and not IIII?
Because the converter always writes the standard shortest form, using subtractive pairs in which a smaller letter placed before a larger one is subtracted. That gives IV for 4, IX for 9, XL for 40 and CM for 900.
Is there a Roman numeral for zero?
No. Roman numerals have no symbol for zero, so the smallest value you can convert is 1. Zero, negative numbers and fractions fall outside the range and are rejected.
Can I type a Roman numeral in lowercase?
Yes. The text is turned into capitals before it is read, so mmxxvi and MMXXVI give the same result. What matters is the spelling: a form that is not standard, such as IIII, is rejected.
How is a Roman numeral read back into a number?
From left to right: a two-letter subtractive pair such as CM or IX is matched first, otherwise a single letter, and the values are added up. That is handy for sorting old references numbered I, II, III.
Related tools
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/dev/roman \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"mode":"to_roman","value":"2026"}'const res = await fetch("https://api.kit.forhosting.com/dev/roman", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"mode": "to_roman",
"value": "2026"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/dev/roman",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"mode": "to_roman",
"value": "2026"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/dev/roman", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"mode":"to_roman","value":"2026"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"mode":"to_roman","value":"2026"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/dev/roman", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"mode": "to_roman",
"value": "2026"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "dev.roman",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
min | 1 |
max | 3999 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |
Technical notes
Converts between Arabic integers (1 to 3999) and Roman numerals in two directions. mode=to_roman turns a number into its Roman string; mode=to_arabic parses a Roman string back to a number and rejects any non-canonical spelling.
Greedy subtractive conversion
Both directions use one ordered value table of thirteen symbol pairs: 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. mode=to_roman walks that table from largest to smallest, appending each symbol while the remaining value is at least its weight and subtracting as it goes, so 2026 becomes MMXXVI and 4 becomes IV rather than IIII. mode=to_arabic scans the string left to right, matching a two-character subtractive pair (like CM or IX) before falling back to a single symbol, summing the weights. This greedy scheme is why the output is always the standard shortest form with proper subtractive notation instead of additive repetition.
Input handling and the 1..3999 range
There is no zero and no numeral for it, and 3999 (MMMCMXCIX) is the largest value expressible with the classic symbols. The value field is documented as a string but to_roman also accepts a plain integer or an all-digit string, which it coerces with Number before checking that it is an integer within 1..3999; anything outside that inclusive range throws invalid_input. For to_arabic the value must be a string; it is upper-cased first, so mixed or lower case like "mmxxvi" is accepted, then screened so only the letters M, D, C, L, X, V and I may appear. The response echoes the normalized input string and the converted result.
Strict canonical validation on parse
to_arabic does more than sum symbols: after parsing it re-encodes the number with to_roman and requires the result to equal the upper-cased input exactly, and it also confirms the parsed value lands in 1..3999. That round-trip check rejects malformed but summable strings such as IIII, VV or IC, which naive parsers would silently accept, because only the one canonical spelling survives the re-encode. Any stray character, an empty string, or a value out of range raises a typed invalid_input error rather than an untyped crash.
Integration use cases
Validating user-entered numerals
Use to_arabic to accept a Roman string only if it is written in strict canonical form, rejecting IIII or IC.
Technical questions
Does it accept lowercase Roman input?
Yes. to_arabic upper-cases the string first, so "mmxxvi" is treated the same as "MMXXVI".
Will it accept IIII or IC?
No. After parsing it re-encodes the number and requires an exact match, so only the one canonical spelling is valid; non-standard forms raise invalid_input.
Can I pass the number as a string?
For to_roman, yes: an all-digit string like "1990" is coerced to a number. to_arabic expects a Roman-letter string.
Is it free and what does the API cost?
The browser widget is free; the API uses prepaid KIT balance from $0.002, with failed tasks not charged.