diff --git a/src/main/java/kyu4/RomanNumerals.java b/src/main/java/kyu4/RomanNumerals.java index 3dbf316..4a7c5f4 100644 --- a/src/main/java/kyu4/RomanNumerals.java +++ b/src/main/java/kyu4/RomanNumerals.java @@ -84,15 +84,22 @@ public static String toRoman(int n) { } public static int fromRoman(String romanNumeral) { + if (romanNumeral == null) { + throw new IllegalArgumentException("Roman numeral must not be null"); + } + // Traverse from right to left: add when symbol value is >= previous, // subtract otherwise. This naturally applies the subtractive rule. StringBuilder stringBuilderReverse = new StringBuilder(romanNumeral).reverse(); char[] chars = stringBuilderReverse.toString().toCharArray(); - int result = 0; + long result = 0; int previous = 0; for (char c : chars ) { Integer integer = MAP_FROM.get(c); + if (integer == null) { + throw new IllegalArgumentException("Unsupported Roman numeral character: " + c); + } if (integer >= previous) { result += integer; } else { @@ -101,7 +108,7 @@ public static int fromRoman(String romanNumeral) { previous = integer; } - return result; + return Math.toIntExact(result); } diff --git a/src/test/java/kyu4/RomanNumeralsTest.java b/src/test/java/kyu4/RomanNumeralsTest.java index b2f2904..1685c26 100644 --- a/src/test/java/kyu4/RomanNumeralsTest.java +++ b/src/test/java/kyu4/RomanNumeralsTest.java @@ -1,9 +1,11 @@ package kyu4; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.stream.Stream; import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -30,6 +32,19 @@ void shouldRoundTripCanonicalRomanNumerals(int value) { assertEquals(value, RomanNumerals.fromRoman(RomanNumerals.toRoman(value))); } + @ParameterizedTest + @MethodSource("invalidRomanNumerals") + void shouldRejectInvalidRomanNumerals(String romanNumeral) { + assertThrows(IllegalArgumentException.class, () -> RomanNumerals.fromRoman(romanNumeral)); + } + + @Test + void shouldRejectResultsOutsideIntegerRange() { + String romanNumeral = "M".repeat(2_147_484); + + assertThrows(ArithmeticException.class, () -> RomanNumerals.fromRoman(romanNumeral)); + } + private static Stream romanCases() { return Stream.of( Arguments.of(1, "I"), @@ -45,4 +60,8 @@ private static Stream romanCases() { Arguments.of(3_999, "MMMCMXCIX") ); } + + private static Stream invalidRomanNumerals() { + return Stream.of((String) null, "A", "MCMX?"); + } }