1. 概述
罗马数字是古罗马人发明的一种计数系统,用特定字母代表不同数值。虽然现在日常使用不多,但在钟表、书籍章节编号、电影出品年份等场景仍能看到它的身影。
本文将带你实现一个简单高效的双向转换器,支持罗马数字 ↔ 阿拉伯数字之间的互转。代码简洁、逻辑清晰,适合集合备用。
2. 罗马数字基础
罗马数字由 7 个基本符号构成,每个符号对应一个固定值:
- ✅
I
= 1 - ✅
V
= 5 - ✅
X
= 10 - ✅
L
= 50 - ✅
C
= 100 - ✅
D
= 500 - ✅
M
= 1000
早期人们用 IIII
表示 4,XXXX
表示 40,但这种写法容易误读(比如看成 III)。为提升可读性,后来引入了减法规则(subtractive notation):
- ❌
IIII
→ ✅IV
(表示 5 - 1) - ❌
XXXX
→ ✅XL
(表示 50 - 10) - ❌
CCCC
→ ✅CD
(表示 500 - 100)
⚠️ 这个细节很关键!意味着我们在解析时不能简单地逐字符相加,必须向前看一位,判断是否构成“减法组合”。
3. 数据模型设计
为了方便处理减法情况,我们直接在枚举中预定义所有合法符号组合,包括 IV
, IX
, XL
, XC
, CD
, CM
。
enum RomanNumeral {
I(1), IV(4), V(5), IX(9), X(10),
XL(40), L(50), XC(90), C(100),
CD(400), D(500), CM(900), M(1000);
private int value;
RomanNumeral(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static List<RomanNumeral> getReverseSortedValues() {
return Arrays.stream(values())
.sorted(Comparator.comparing((RomanNumeral e) -> e.value).reversed())
.collect(Collectors.toList());
}
}
📌 关键点说明:
- ✅
getReverseSortedValues()
返回按数值从大到小排序的符号列表 - ✅ 转换时优先匹配大值符号,简单粗暴且高效
- ✅ 把
IV
,IX
等作为独立枚举项,避免手动判断减法规则,减少出错概率
4. 罗马数字转阿拉伯数字
罗马数字表示范围通常是 1 ~ 3999(极少数扩展到4000),我们这里也限制在这个区间。
4.1 实现思路
核心算法:贪心匹配 + 字符串前缀判断
输入:一个罗马数字字符串
初始化结果为 0
从最大符号开始遍历(M → I)
如果当前字符串以该符号命名开头:
将其对应数值加到结果
去掉字符串开头这部分
否则:
尝试下一个更小的符号
直到字符串为空或遍历完所有符号
4.2 Java 实现
public static int romanToArabic(String input) {
String romanNumeral = input.toUpperCase();
int result = 0;
List<RomanNumeral> romanNumerals = RomanNumeral.getReverseSortedValues();
int i = 0;
while ((romanNumeral.length() > 0) && (i < romanNumerals.size())) {
RomanNumeral symbol = romanNumerals.get(i);
if (romanNumeral.startsWith(symbol.name())) {
result += symbol.getValue();
romanNumeral = romanNumeral.substring(symbol.name().length());
} else {
i++;
}
}
if (romanNumeral.length() > 0) {
throw new IllegalArgumentException(input + " 不是合法的罗马数字");
}
return result;
}
📌 踩坑提醒:
- 必须先转成大写(
toUpperCase()
),避免大小写问题 - 最后检查
romanNumeral.length() > 0
,防止非法输入如"MXIIII"
或"ABC"
- 使用
startsWith()
和substring()
配合,逻辑清晰不易错
4.3 测试验证
@Test
void given2018Roman_WhenConvertingToArabic_ThenReturn2018() {
String roman2018 = "MMXVIII";
int result = RomanArabicConverter.romanToArabic(roman2018);
assertThat(result).isEqualTo(2018);
}
✅ 输出:2018
—— 转换正确!
5. 阿拉伯数字转罗马数字
反过来也一样,使用贪心策略:每次都选不超过当前数值的最大符号。
5.1 实现思路
输入:一个 1~3999 的整数
初始化结果为空字符串
从最大符号开始遍历(M → I)
如果当前符号的值 ≤ 剩余数值:
把该符号追加到结果中
从剩余数值中减去该符号的值
否则:
尝试下一个更小的符号
重复直到数值归零
5.2 Java 实现
public static String arabicToRoman(int number) {
if ((number <= 0) || (number > 4000)) {
throw new IllegalArgumentException(number + " 超出支持范围 (1~3999)");
}
List<RomanNumeral> romanNumerals = RomanNumeral.getReverseSortedValues();
int i = 0;
StringBuilder sb = new StringBuilder();
while ((number > 0) && (i < romanNumerals.size())) {
RomanNumeral currentSymbol = romanNumerals.get(i);
if (currentSymbol.getValue() <= number) {
sb.append(currentSymbol.name());
number -= currentSymbol.getValue();
} else {
i++;
}
}
return sb.toString();
}
📌 优点:
- ✅ 利用预定义的
getReverseSortedValues()
,天然支持贪心匹配 - ✅
StringBuilder
提高性能 - ✅ 逻辑对称统一,和上一个方法结构几乎一致,便于维护
5.3 测试验证
@Test
void given1999Arabic_WhenConvertingToRoman_ThenReturnMCMXCIX() {
int arabic1999 = 1999;
String result = RomanArabicConverter.arabicToRoman(arabic1999);
assertThat(result).isEqualTo("MCMXCIX");
}
✅ 输出:MCMXCIX
—— 正确表示 1999(1000 + 900 + 90 + 9)
6. 总结
本文实现了一个双向罗马数字转换器,具备以下特点:
- ✅ 使用
enum
统一管理符号和值,结构清晰 - ✅ 支持减法规则(IV, IX 等),无需额外逻辑判断
- ✅ 两个转换方法均采用贪心策略,代码简洁高效
- ✅ 包含边界检查和异常处理,健壮性强
- ✅ 单元测试覆盖典型用例,放心使用
完整代码已托管至 GitHub:https://github.com/johnchen902/RomanNumeralConverter
这类小工具看似简单,但在实际项目中偶尔会用到(比如生成文档标题编号、解析历史数据等),建议集合备用。