unit RomanNumerals;

interface

uses
  SysUtils;

function RomanToInt(const S: string): UInt64;
function IntToRoman(N: UInt64): string;

implementation

uses
  Math;

function RomanToInt(const S: string): UInt64;
const
  I = 1;
  V = 5;
  X = 10;
  L = 50;
  C = 100;
  D = 500;
  M = 1000;
  ChrVals: array[Ord('A') .. Ord('Z')] of Cardinal =
    (
      // A   B   C   D   E   F   G   H   I   J   K   L   M   N   O   P   Q   R   S   T   U   V   W   X   Y   Z
         0,  0, 100,500, 0,  0,  0,  0,  1,  0,  0, 50,1000, 0,  0,  0,  0,  0,  0,  0,  0,  5,  0, 10,  0,  0
    );
  bp: array[0..6] of UInt64 = (1, 1_000, 1_000_000, 1_000_000_000, 1_000_000_000_000, 1_000_000_000_000_000, 1_000_000_000_000_000_000);
begin
  Result := 0;
  const T = S.Trim.ToUpper;
  var block := 0;
  var max: Cardinal := 0;
  for var k := T.Length downto 1 do
  begin
    if InRange(Ord(T[k]), Low(ChrVals), High(ChrVals)) then
    begin
      const val = ChrVals[Ord(T[k])];
      if (val = 0) and (T[k] <> 'N') then
        raise EConvertError.Create('Invalid character in Roman numeral.');
      if val >= max then
        Inc(Result, val * bp[block])
      else
        Dec(Result, val * bp[block]);
      if val > max then
        max := val;
    end
    else if T[k] = '''' then
    begin
      Inc(block);
      max := 0;
    end
    else
      raise EConvertError.Create('Invalid character in Roman numeral.');
  end;
end;

function IntToRoman(N: UInt64): string;
const
  Parts: array[0..3] of array[0..9] of string =
    (
      ('', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'),
      ('', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'),
      ('', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM'),
      ('', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM', 'MMMMMM', 'MMMMMMM', 'MMMMMMMM', 'MMMMMMMMM')
    );
  procedure HandleBlock;
  begin
    var Block := '';
    if N div 1000 mod 10 <= 4 then
      Block :=
        Parts[3, N div 1000 mod 10] +
        Parts[2, N div 100 mod 10] +
        Parts[1, N div 10 mod 10] +
        Parts[0, N mod 10]
    else
      Block :=
        Parts[2, N div 100 mod 10] +
        Parts[1, N div 10 mod 10] +
        Parts[0, N mod 10];
    Result := Block + Result;
    Dec(N, RomanToInt(Block));
    if N mod 1000 <> 0 then
      raise Exception.Create('IntToRoman: Unexpected state.');
    N := N div 1000;
    if N <> 0 then
      Result := '''' + Result;
  end;
begin
  if N = 0 then
    Exit('N');
  Result := '';
  while N <> 0 do
    HandleBlock;
end;

end.
