diff --git a/src/Humanizer/RomanNumeralExtensions.cs b/src/Humanizer/RomanNumeralExtensions.cs
index b16528eef..6b6f91533 100644
--- a/src/Humanizer/RomanNumeralExtensions.cs
+++ b/src/Humanizer/RomanNumeralExtensions.cs
@@ -1,5 +1,7 @@
// Done by Jesse Slicer https://github.com/jslicer
+using System.Diagnostics;
+
namespace Humanizer;
///
@@ -7,54 +9,42 @@ namespace Humanizer;
///
public static class RomanNumeralExtensions
{
- static readonly Dictionary RomanNumerals =
- new(StringComparer.OrdinalIgnoreCase)
+ static readonly KeyValuePair[] RomanNumeralsSequence =
+ [
+ new KeyValuePair("M", 1000),
+ new KeyValuePair("CM", 900),
+ new KeyValuePair("D", 500 ),
+ new KeyValuePair("CD", 400),
+ new KeyValuePair("C", 100 ),
+ new KeyValuePair("XC", 90 ),
+ new KeyValuePair("L", 50 ),
+ new KeyValuePair("XL", 40 ),
+ new KeyValuePair("X", 10 ),
+ new KeyValuePair("IX", 9 ),
+ new KeyValuePair("V", 5 ),
+ new KeyValuePair("IV", 4 ),
+ new KeyValuePair("I", 1 ),
+ ];
+
+ static int GetRomanNumeralCharValue(char c)
+ {
+ Debug.Assert(char.ToLowerInvariant(c) is 'M' or 'D' or 'C' or 'L' or 'X' or 'V' or 'I', "Invalid Roman numeral character");
+ return (c & ~0x20) switch
{
- {
- "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
- }
+ 'M' => 1000,
+ 'D' => 500,
+ 'C' => 100,
+ 'L' => 50,
+ 'X' => 10,
+ 'V' => 5,
+ _ => 1,
};
+ }
static readonly Regex ValidRomanNumeral =
new(
"^(?i:(?=[MDCLXVI])((M{0,3})((C[DM])|(D?C{0,3}))?((X[LC])|(L?XX{0,2})|L)?((I[VX])|(V?(II{0,2}))|V)?))$",
- RegexOptions.Compiled | RegexOptions.IgnoreCase);
+ RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
///
/// Converts Roman numbers into integer
@@ -92,14 +82,10 @@ public static int FromRoman(CharSpan input)
while (i > 0)
{
- var digit = RomanNumerals[input[--i]
- .ToString()];
-
+ var digit = GetRomanNumeralCharValue(input[--i]);
if (i > 0)
{
- var previousDigit = RomanNumerals[input[i - 1]
- .ToString()];
-
+ var previousDigit = GetRomanNumeralCharValue(input[i - 1]);
if (previousDigit < digit)
{
digit -= previousDigit;
@@ -130,18 +116,20 @@ public static string ToRoman(this int input)
throw new ArgumentOutOfRangeException();
}
- var builder = new StringBuilder(maxRomanNumeralLength);
+ Span builder = stackalloc char[maxRomanNumeralLength];
+ var pos = 0;
- foreach (var pair in RomanNumerals)
+ foreach (var pair in RomanNumeralsSequence)
{
- while (input / pair.Value > 0)
+ while (input >= pair.Value)
{
- builder.Append(pair.Key);
+ pair.Key.AsSpan().CopyTo(builder.Slice(pos));
+ pos += pair.Key.Length;
input -= pair.Value;
}
}
- return builder.ToString();
+ return builder.Slice(0, pos).ToString();
}
static bool IsInvalidRomanNumeral(CharSpan input) =>