language-Modula2-0.1: examples/Modula-2_Libraries/andrea-m2/lib/generic/romannume.mod
IMPLEMENTATION MODULE RomanNumerals;
(* convert to/from strings of Roman Numerals to cardinal values *)
(* J. Andrea, Jun.4/92 - more compact numbers, and more correct *)
(* J. Andrea, Nov.9/91 - better ToRoman *)
(* J. Andrea, Sept.6/91 *)
(* This code may be freely used and distributed, it may not be sold. *)
FROM StringOperations IMPORT Index, Length;
CONST
nul = 0C;
VAR
Numerals :ARRAY [1..7] OF CHAR;
Values :ARRAY [1..7] OF CARDINAL;
i, k :CARDINAL;
ok :BOOLEAN;
len, max_len :CARDINAL;
(* -------------------------------------------------------------- *)
PROCEDURE FromRoman( roman :ARRAY OF CHAR; VAR value :CARDINAL );
CONST
max_groups = 10; (* this may not be a valid assumption *)
VAR
c, prev :CHAR;
group :ARRAY [1..max_groups] OF CARDINAL;
n_group :CARDINAL;
prev_group, curr_group :CARDINAL;
BEGIN
len := Length( roman );
IF len = 0 THEN
value := 0;
ELSE
(* in this first section, break the roman numeral into groups *)
(* of same values *)
n_group := 0; prev := nul;
i := 0; ok := TRUE;
WHILE ok & ( i < len ) DO
c := CAP( roman[i] );
k := Index( Numerals, c );
IF k = 0 THEN
ok := FALSE;
ELSE
IF c = prev THEN
(* same group *)
group[n_group] := group[n_group] + Values[k];
ELSE
(* new group *)
n_group := n_group + 1;
IF n_group > max_groups THEN
(* algorithm error *)
ok := FALSE;
ELSE
group[n_group] := Values[k];
prev := c;
END;
END;
i := i + 1;
END;
END;
value := 0;
IF ok THEN
IF n_group = 1 THEN
value := group[1];
ELSE
(* force the last group to be zero *)
n_group := n_group + 1;
group[n_group] := 0;
(* now add of subtract the groups to form a number *)
prev_group := group[1];
i := 2;
WHILE i <= n_group DO
curr_group := group[i];
IF prev_group < curr_group THEN
prev_group := curr_group - prev_group;
ELSE
value := value + prev_group;
prev_group := curr_group;
END;
i := i + 1;
END;
END;
END;
END;
END FromRoman;
(* -------------------------------------------------------------- *)
PROCEDURE ToRoman( value :CARDINAL; VAR roman :ARRAY OF CHAR );
VAR
s, subtract :CARDINAL;
(* -------------------------------------------------------------- *)
PROCEDURE AddChar( c :CHAR );
BEGIN
IF len <= max_len THEN
roman[len] := c; len := len + 1;
ELSE
ok := FALSE;
END;
END AddChar;
PROCEDURE InitChar;
BEGIN
len := 0;
max_len := HIGH( roman );
ok := TRUE;
END InitChar;
PROCEDURE EndChar;
BEGIN
IF len <= max_len THEN roman[len] := nul; END;
END EndChar;
BEGIN
InitChar;
IF value > 0 THEN
(* remove any large numbers first *)
k := 7;
WHILE ok & ( value >= Values[k] ) DO
AddChar( Numerals[k] );
value := value - Values[k];
END;
(* determine the compound numeral string that represents the value *)
k := 7;
WHILE ok & ( value > 0 ) DO
(* special case for anything less than five *)
IF value < 5 THEN
IF value = 4 THEN
AddChar( 'I' ); AddChar( 'V' );
ELSE
FOR i := 1 TO value DO
AddChar( 'I' );
END;
END;
value := 0;
ELSE
(* find numeral which is less than or equal to the current value *)
WHILE Values[k] > value DO
k := k - 1;
END;
IF value = Values[k] THEN
(* its an exact match *)
AddChar( Numerals[k] );
value := 0;
ELSE
(* see if this next value can be made up of a roman subtraction *)
(* keeping in mind that only 1, 10, 100, ... can be subtracted *)
(* from anything, which are the numerals with odd indicies *)
(* and you can only subtract one level away, eg. not 1 from 100 *)
i := k + 1; (* the next larger numeral *)
IF ODD( i ) THEN
s := i - 2;
ELSE
s := i - 1;
END;
subtract := Values[i] - Values[s];
IF subtract > value THEN
(* not a numeral subtract (IX, IV, etc.), so just do a subtract *)
value := value - Values[k];
AddChar( Numerals[k] );
ELSE
(* it is a subtract, do it *)
value := value - subtract;
AddChar( Numerals[s] ); AddChar( Numerals[i] );
END;
END;
END;
END; (* while *)
END;
IF ok THEN
EndChar;
ELSE
(* something wrong, return an empty string *)
roman[0] := nul;
END;
END ToRoman;
BEGIN
Numerals[1] := 'I'; Values[1] := 1;
Numerals[2] := 'V'; Values[2] := 5;
Numerals[3] := 'X'; Values[3] := 10;
Numerals[4] := 'L'; Values[4] := 50;
Numerals[5] := 'C'; Values[5] := 100;
Numerals[6] := 'D'; Values[6] := 500;
Numerals[7] := 'M'; Values[7] := 1000;
END RomanNumerals.