Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2013-09

StrUtils.RomanToInt oddities

48 messages · 9 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Bart

Hi,

RomanToInt acceps rather ludicrous values:

RomanToInt('MDCLXVIVXLDM') = 2209
RomanToInt('IIIIM') = 1002 //calculated as 3 + (1000-1)

Both examples represent invalid roman numbers by any standard. Also I do not think Roman numerals can be negative...

Feature or bug?

Bart

Reinier Olislagers
to Bart
Bart wrote 10 lines
RomanToInt acceps rather ludicrous values: RomanToInt('MDCLXVIVXLDM') = 2209 RomanToInt('IIIIM') = 1002 //calculated as 3 + (1000-1) Both examples represent invalid roman numbers by any standard. Also I do not think Roman numerals can be negative... Feature or bug?

1. I'll respond with the expected answer: "What does Delphi do" (if it has this function?
2. I wouldn't be bothered by negative numbers... even the Romans used to have negative posessions (= debts) and they didn't have double entry bookkeeping yet ;) Also, the documentation [1] mentions it as a feature.
3. The rest... well, depending on 1. I'd agree it's a bug. [1] mentions "Invalid characters are dropped from S"... which I think is a bit strange (S is a const) but assuming it means "Invalid characters in S are ignored" it means somebody thought checking the input for valid Roman numbers was a good idea. The question however becomes "what is the algorithm for deciding invalid characters" which IMO will become a mess very quickly. Much better to just consider the entire input as invalid.

[1] http://lazarus-ccr.sourceforge.net/docs/rtl/strutils/romantoint.html

Bart
to Reinier Olislagers
Reinier Olislagers wrote
1. I'll respond with the expected answer: "What does Delphi do" (if it has this function?

D7 PE does not have it.

2. I wouldn't be bothered by negative numbers... even the Romans used to have negative posessions (= debts) and they didn't have double entry bookkeeping yet ;) Also, the documentation [1] mentions it as a feature.

If I mention in documentationn that result is unpredicted for any input e.g. for function StrToInt(S), because ATMI implemented it like Result := StringOfChar(Char(Random(256)),Random(256)), then this would also not considered to be a bug, just it because does what the docs say?

Show quoted text 7 lines
3. The rest... well, depending on 1. I'd agree it's a bug. [1] mentions "Invalid characters are dropped from S"... which I think is a bit strange (S is a const) but assuming it means "Invalid characters in S are ignored" it means somebody thought checking the input for valid Roman numbers was a good idea. The question however becomes "what is the algorithm for deciding invalid characters" which IMO will become a mess very quickly. Much better to just consider the entire input as invalid.
Any invalid char in S will result in Result being 0;
So doc is wrong about that.

Bart

Reinier Olislagers
to Bart: D7 PE does not have it. If I mention in documentationn that result is unpredicted for any…
Bart wrote 9 lines
On 9/20/13, Reinier Olislagers wrote: > 2. I wouldn't be bothered by negative numbers... even the Romans used to > have negative posessions (= debts) and they didn't have double entry > bookkeeping yet ;) Also, the documentation [1] mentions it as a feature. If I mention in documentationn that result is unpredicted for any input [snip irrelevant part] then this would also not considered to be a bug, just it because does what the docs say?

Well, if you want a black and white answer: yes, because the implementation follows the documentation/specifcations.

However, as I indicated in the rest of my post, I do think the implementation(+documentation) needs to be changed for other cases. I just don't see that many problems by allowing negative numbers.

Show quoted text 10 lines
> 3. The rest... well, depending on 1. I'd agree it's a bug. > [1] mentions "Invalid characters are dropped from S"... which I think is > a bit strange (S is a const) but assuming it means "Invalid characters > in S are ignored" it means somebody thought checking the input for valid > Roman numbers was a good idea. The question however becomes "what is the > algorithm for deciding invalid characters" which IMO will become a mess > very quickly. Much better to just consider the entire input as invalid. Any invalid char in S will result in Result being 0; So doc is wrong about that.

Seems we agree here:
1. Changing docs to mention any invalid character will lead to result being 0. No idea what the policy is about needing to raise exceptions in case of errors - in this particular case, returning 0 should actually be enough by itself as there is no Roman numeral that stands for the value 0...
2. The other cases you mentioned in your OP are bugs, not features.

Bart
to Reinier Olislagers
Reinier Olislagers wrote
2. The other cases you mentioned in your OP are bugs, not features.

Unless the Greek do the same, someone with recent Delphi should test. If that's the case we're doomed to follw that.

Bart

Bart
to Bart

And please, before anyone gets offended by my postings in this thread:

- I just stubled on this whils doing a kind WTF implementation of something like this (and at that time I didn't even knew we had StrUtils.RomanToInt).
- I don't think it is the end of the world just because the StrUtils version behaves different than I expected it to.
- I do not want to pretend my interpretation of "valid Roman numeral" is the absolute truth.

I was just curious as to the why?

I have my own interpretation (in code), so if fpc's stays as is, no problem whatsoever.
It (my version) is LGPL, so anyone can use and or contribute.

Sometimes I just like having these kind of discussions...

Bart

Bart
to Reinier Olislagers: 1. I'll respond with the expected answer: "What does Delphi do" (if it has this function?…
Reinier Olislagers wrote
The question however becomes "what is the algorithm for deciding invalid characters" which IMO will become a mess very quickly. Much better to just consider the entire input as invalid.

Here's my implementation:

program test;
{$mode objfpc}
{$H+}
uses
  SysUtils, StrUtils;
function TryRomanToInt(S: String; out N: Integer): Boolean;
var
  i, Len: Integer;
  Terminated: Boolean;
begin
  Result := (False);
  S := UpperCase(S);  //don't use AnsiUpperCase please
  Len := Length(S);
  if (Len = 0) then Exit;
  i := 1;
  N := 0;
  Terminated := False;
  //leading M's
  while (i <= Len) and (S[i] = 'M') do
  begin
    //writeln('TryRomanToInt: Found 1000');
    Inc(i);
    N := N + 1000;
  end;
  //then CM or or CD or D or (C, CC, CCC, CCCC)
  if (i <= Len) and (S[i] = 'D') then
  begin
    //writeln('TryRomanToInt: Found 500');
    Inc(i);
    N := N + 500;
  end
  else if (i + 1 <= Len) and (S[i] = 'C') then
  begin
    if (S[i+1] = 'M') then
    begin
      //writeln('TryRomanToInt: Found 900');
      Inc(i,2);
      N := N + 900;
    end
    else if (S[i+1] = 'D') then
    begin
      //writeln('TryRomanToInt: Found 400');
      Inc(i,2);
      N := N + 400;
    end;
  end ;
  //next max 4 C's
  if (i <= Len) and (S[i] = 'C') then
  begin
    //find max 4 C's
    //writeln('TryRomanToInt: Found 100');
    Inc(i);
    N := N + 100;
    if (i <= Len) and (S[i] = 'C') then
    begin
      //writeln('TryRomanToInt: Found 100');
      Inc(i);
      N := N + 100;
    end;
    if (i <= Len) and (S[i] = 'C') then
    begin
      //writeln('TryRomanToInt: Found 100');
      Inc(i);
      N := N + 100;
    end;
    if (i <= Len) and (S[i] = 'C') then
    begin
      //writeln('TryRomanToInt: Found 100');
      Inc(i);
      N := N + 100;
    end;
  end;
  //then XC or XL
  if (i + 1 <= Len) and (S[i] = 'X') then
  begin
    if (S[i+1] = 'C') then
    begin
      //writeln('TryRomanToInt: Found 90');
      Inc(i,2);
      N := N + 90;
    end
    else if  (S[i+1] = 'L') then
    begin
      //writeln('TryRomanToInt: Found 40');
      Inc(i,2);
      N := N + 40;
    end;
  end;
  //then L
  if (i <= Len) and (S[i] = 'L') then
  begin
    //writeln('TryRomanToInt: Found 50');
    Inc(i);
    N := N + 50;
  end;
  //then (X, xx, xxx, xxxx)
  if (i <= Len) and (S[i] = 'X') then
  begin
    //find max 4 X's
    //writeln('TryRomanToInt: Found 10');
    Inc(i);
    N := N + 10;
    if (i <= Len) and (S[i] = 'X') then
    begin
      //writeln('TryRomanToInt: Found 10');
      Inc(i);
      N := N + 10;
    end;
    if (i <= Len) and (S[i] = 'X') then
    begin
      //writeln('TryRomanToInt: Found 10');
      Inc(i);
      N := N + 10;
    end;
    if (i <= Len) and (S[i] = 'X') then
    begin
      //writeln('TryRomanToInt: Found 10');
      Inc(i);
      N := N + 10;
    end;
  end;
  //then IX or IV
  if (i + 1 <= Len) and (S[i] = 'I') then
  begin
    if (S[i+1] = 'X') then
    begin
      Terminated := (True);
      //writeln('TryRomanToInt: Found 9');
      Inc(i,2);
      N := N + 9;
    end
    else if (S[i+1] = 'V') then
    begin
      Terminated := (True);
      //writeln('TryRomanToInt: Found 4');
      Inc(i,2);
      N := N + 4;
    end;
  end;
  //then V
  if (not Terminated) and (i <= Len) and (S[i] = 'V') then
  begin
    //writeln('TryRomanToInt: Found 5');
    Inc(i);
    N := N + 5;
  end;
  //then I
  if (not Terminated) and (i <= Len) and (S[i] = 'I') then
  begin
    Terminated := (True);
    //writeln('TryRomanToInt: Found 1');
    Inc(i);
    N := N + 1;
    //Find max 3 closing I's
    if (i <= Len) and (S[i] = 'I') then
    begin
      //writeln('TryRomanToInt: Found 1');
      Inc(i);
      N := N + 1;
    end;
    if (i <= Len) and (S[i] = 'I') then
    begin
      //writeln('TryRomanToInt: Found 1');
      Inc(i);
      N := N + 1;
    end;
    if (i <= Len) and (S[i] = 'I') then
    begin
      //writeln('TryRomanToInt: Found 1');
      Inc(i);
      N := N + 1;
    end;
  end;
  //writeln('TryRomanToInt: Len = ',Len,' i = ',i);
  Result := (i > Len);
  //if Result then writeln('TryRomanToInt: N = ',N);
end;
var
  S: String;
  N1, N2: Integer;
  B: Boolean;
begin
  repeat
    write('Enter Roman numeral: ');
    readln(S);
    B := TryRomanToInt(S, N1);
    if B then
      write('TryRomanToInt(''',S,''') -> ',N1)
    else
      write('TryRomanToInt(''',S,''') FAIL');
    writeln;
    N2 := StrUtils.RomanToInt(S);
    writeln('StrUtils.RomanToInt(''',S,''') = ',N2);
    if B and (N1  N2) then writeln('StrUtils.RomanToInt  TryRomanToInt');
    writeln;
  until S = '';
end.

Bart

Lukasz Sokol
to Bart
Bart wrote 8 lines
On 9/20/13, Reinier Olislagers wrote: > The question however becomes "what is the > algorithm for deciding invalid characters" which IMO will become a mess > very quickly. Much better to just consider the entire input as invalid. > Here's my implementation:

Here is how I would write it ;)

function TryRomanToInt(AInput:String; out AResult: integer):boolean;
var i, Len, N, Np1 : integer;
  function TryRomanCharToInt(AIn: Char; out AOut: integer): boolean;
  begin
    case AIn of
      'M' : AOut := 1000;
      'D' : AOut := 500;
      'C' : AOut := 100;
      'L' : AOut := 50;
      'X' : AOut := 10;
      'V' : AOut := 5;
      'I' : AOut := 1;
    else
      AOut := 0;
    end;
    Result := (AOut > 0);
  end;
begin
  // if it looks like shameless c&p, it's because it is ;)
  Result := False;
  AInput := UpperCase(AInput);  //don't use AnsiUpperCase please
  Len := Length(AInput);
  if (Len = 0) then Exit;
  //
  i := 1;
  for i := 1 to Len-1 do
  begin
    if not TryRomanCharToInt(AInput[i], N) then
    begin
      AResult := -1; // invalidate everything
      exit;
    // writeln('Not a valid roman numeral at position ',i);
    end;
    if (i > Len -1) then
      begin
        if not TryRomanCharToInt(AInput[i+1], Np1) then
        begin
          AResult := -1; // invalidate everithing
          exit;
        // writeln('Not a valid roman numeral at position ',i+1);
        end;
	// according to wikipedia, wonder if these are hard or soft rules thou...
        if not (((N = 100) and (Np1 > 100)) or  // C can be placed before L or M
                ((N = 10) and (Np1 > 10) and (Np1 <= 100)) or // X can be placed before L or C
                ((N = 1) and (Np1 > 1) and (Np1 <= 10)))  // I can be placed before V and X
        then
          begin
            AResult := -1; // invalidate everithing: catches MDCLXVIVXLDM
            exit;
            // writeln('Not a valid roman numeral combination at position ',i, ' and ',i+1);
          end;
        if N >= Np1 then AResult := AResult + N
        else AResult := AResult - N;
      end;
    else // i = Len-1 = last char we just add (consider : MCM : add 1000, sub 100, add 1000 = 1900)
     begin
       AResult := AResult + N;
     end;
  end; // for
  // if the above, after all characters are through, has resulted in 0 or less,
  // we invalidate everything at the end (consider : CMLM, IIIM )
  Result := AResult > 0;
  if not Result then AResult := 0;
end;

(only mind-compiled ;) tests welcome ;) )

-L

Show quoted text 219 lines
program test; {$mode objfpc} {$H+} uses SysUtils, StrUtils; function TryRomanToInt(S: String; out N: Integer): Boolean; var i, Len: Integer; Terminated: Boolean; begin Result := (False); S := UpperCase(S); //don't use AnsiUpperCase please Len := Length(S); if (Len = 0) then Exit; i := 1; N := 0; Terminated := False; //leading M's while (i <= Len) and (S[i] = 'M') do begin //writeln('TryRomanToInt: Found 1000'); Inc(i); N := N + 1000; end; //then CM or or CD or D or (C, CC, CCC, CCCC) if (i <= Len) and (S[i] = 'D') then begin //writeln('TryRomanToInt: Found 500'); Inc(i); N := N + 500; end else if (i + 1 <= Len) and (S[i] = 'C') then begin if (S[i+1] = 'M') then begin //writeln('TryRomanToInt: Found 900'); Inc(i,2); N := N + 900; end else if (S[i+1] = 'D') then begin //writeln('TryRomanToInt: Found 400'); Inc(i,2); N := N + 400; end; end ; //next max 4 C's if (i <= Len) and (S[i] = 'C') then begin //find max 4 C's //writeln('TryRomanToInt: Found 100'); Inc(i); N := N + 100; if (i <= Len) and (S[i] = 'C') then begin //writeln('TryRomanToInt: Found 100'); Inc(i); N := N + 100; end; if (i <= Len) and (S[i] = 'C') then begin //writeln('TryRomanToInt: Found 100'); Inc(i); N := N + 100; end; if (i <= Len) and (S[i] = 'C') then begin //writeln('TryRomanToInt: Found 100'); Inc(i); N := N + 100; end; end; //then XC or XL if (i + 1 <= Len) and (S[i] = 'X') then begin if (S[i+1] = 'C') then begin //writeln('TryRomanToInt: Found 90'); Inc(i,2); N := N + 90; end else if (S[i+1] = 'L') then begin //writeln('TryRomanToInt: Found 40'); Inc(i,2); N := N + 40; end; end; //then L if (i <= Len) and (S[i] = 'L') then begin //writeln('TryRomanToInt: Found 50'); Inc(i); N := N + 50; end; //then (X, xx, xxx, xxxx) if (i <= Len) and (S[i] = 'X') then begin //find max 4 X's //writeln('TryRomanToInt: Found 10'); Inc(i); N := N + 10; if (i <= Len) and (S[i] = 'X') then begin //writeln('TryRomanToInt: Found 10'); Inc(i); N := N + 10; end; if (i <= Len) and (S[i] = 'X') then begin //writeln('TryRomanToInt: Found 10'); Inc(i); N := N + 10; end; if (i <= Len) and (S[i] = 'X') then begin //writeln('TryRomanToInt: Found 10'); Inc(i); N := N + 10; end; end; //then IX or IV if (i + 1 <= Len) and (S[i] = 'I') then begin if (S[i+1] = 'X') then begin Terminated := (True); //writeln('TryRomanToInt: Found 9'); Inc(i,2); N := N + 9; end else if (S[i+1] = 'V') then begin Terminated := (True); //writeln('TryRomanToInt: Found 4'); Inc(i,2); N := N + 4; end; end; //then V if (not Terminated) and (i <= Len) and (S[i] = 'V') then begin //writeln('TryRomanToInt: Found 5'); Inc(i); N := N + 5; end; //then I if (not Terminated) and (i <= Len) and (S[i] = 'I') then begin Terminated := (True); //writeln('TryRomanToInt: Found 1'); Inc(i); N := N + 1; //Find max 3 closing I's if (i <= Len) and (S[i] = 'I') then begin //writeln('TryRomanToInt: Found 1'); Inc(i); N := N + 1; end; if (i <= Len) and (S[i] = 'I') then begin //writeln('TryRomanToInt: Found 1'); Inc(i); N := N + 1; end; if (i <= Len) and (S[i] = 'I') then begin //writeln('TryRomanToInt: Found 1'); Inc(i); N := N + 1; end; end; //writeln('TryRomanToInt: Len = ',Len,' i = ',i); Result := (i > Len); //if Result then writeln('TryRomanToInt: N = ',N); end; var S: String; N1, N2: Integer; B: Boolean; begin repeat write('Enter Roman numeral: '); readln(S); B := TryRomanToInt(S, N1); if B then write('TryRomanToInt(''',S,''') -> ',N1) else write('TryRomanToInt(''',S,''') FAIL'); writeln; N2 := StrUtils.RomanToInt(S); writeln('StrUtils.RomanToInt(''',S,''') = ',N2); if B and (N1 N2) then writeln('StrUtils.RomanToInt TryRomanToInt'); writeln; until S = ''; end. Bart _______________________________________________ fpc-pascal maillist - http://lists.freepascal.org/mailman/listinfo/fpc-pascal
Bart
to Lukasz Sokol
Lukasz Sokol wrote
function TryRomanToInt(AInput:String; out AResult: integer):boolean; var i, Len, N, Np1 : integer;

[snip]

Show quoted text 7 lines
if N >= Np1 then AResult := AResult + N else AResult := AResult - N; end; else // i = Len-1 = last char we just add (consider : MCM : add 1000,

Compiler doesn't like the else here. I removed the ; after the end 1 line above.

Show quoted text 15 lines
sub 100, add 1000 = 1900) begin AResult := AResult + N; end; end; // for // if the above, after all characters are through, has resulted in 0 or less, // we invalidate everything at the end (consider : CMLM, IIIM ) Result := AResult > 0; if not Result then AResult := 0; end; (only mind-compiled ;) tests welcome ;) )

It produces "odd" output though:

C:\Users\Bart\LazarusProjecten\ConsoleProjecten>test Enter Roman numeral: LL //should be illegal
TryRomanToInt('LL') -> 50
StrUtils.RomanToInt('LL') = 100

Enter Roman numeral: MM
TryRomanToInt('MM') -> 1050 //obviously should be 2000 StrUtils.RomanToInt('MM') = 2000

Enter Roman numeral: II
TryRomanToInt('II') -> 1051 // nice one!
StrUtils.RomanToInt('II') = 2

So, it's a little bit flawed.

Anyhow, let's not focus upon what the new code should be. The question was: is current behaviour (accepting IIMIIC etc.) a bug or not.

Bart

Alberto Narduzzi
to Bart: Here's my implementation: program test; {$mode objfpc} {$H+} uses SysUtils, StrUtils;…

Premise: I didn't go through your entire implementation, thou' at first sight looks much better than the current one.

I know roman numerals since I was 8 or 9 yo, that makes a quick and dirt result of about 35 years.

Thing is:

1. in the roman numerals, not all the digits can be subtracted from the others
2. no more than three same roman numerals can appear in a row.

so that XM is as invalid as CCCC.
(although this amenity can be seen on some - in my opinion poor - wristwatches' face that uses roman numerals, for the 4h, showing IIII instead of the correct IV, but this is another story)

in roman numerals we have only these digits:

I(1) V(5)
X(10) L(50)
C(100) D(500)
M(1000) (sorry, no digit for the expected progression, for 5000)

the only digits that can be subtracted are I, X and C (only those on the left side of the "table" above), and they can only from the 2 digits immediately following them in the system, so that:

I can be subtracted only from V and X
X can be subtracted only from L and C
C can be subtracted only from D and M

so that 45 cannot be written as VL (50 - 5, as one might suspect) but must be written as XLV, or to better understand, as (XL)V = 40 + 5

The maximum roman numeral that can be represented is 4999, that is NOT MMMIM, but instead: MMM(CM)(XC)(IX)
1000+1000+1000+(1000-900)+(100-10)+(10-1)

After this, roman numerals always begin with bigger numbers to sum up, and proceed to smaller, so you cannot start adding up 10, then 500, then again 1; like XDI (whether it be intended to be 491 or 511 - just no way)

I did an implementation some time ago, for pure fun, and provided I find it out (or maybe I can just rewrite it, on the basis of this knowledge) I will be - repository allowing ;-) - wery happy to contribute with my 2c for it.

Cheers, A.

Bart wrote 227 lines
On 9/20/13, Reinier Olislagers wrote: > The question however becomes "what is the > algorithm for deciding invalid characters" which IMO will become a mess > very quickly. Much better to just consider the entire input as invalid. > Here's my implementation: program test; {$mode objfpc} {$H+} uses SysUtils, StrUtils; function TryRomanToInt(S: String; out N: Integer): Boolean; var i, Len: Integer; Terminated: Boolean; begin Result := (False); S := UpperCase(S); //don't use AnsiUpperCase please Len := Length(S); if (Len = 0) then Exit; i := 1; N := 0; Terminated := False; //leading M's while (i<= Len) and (S[i] = 'M') do begin //writeln('TryRomanToInt: Found 1000'); Inc(i); N := N + 1000; end; //then CM or or CD or D or (C, CC, CCC, CCCC) if (i<= Len) and (S[i] = 'D') then begin //writeln('TryRomanToInt: Found 500'); Inc(i); N := N + 500; end else if (i + 1<= Len) and (S[i] = 'C') then begin if (S[i+1] = 'M') then begin //writeln('TryRomanToInt: Found 900'); Inc(i,2); N := N + 900; end else if (S[i+1] = 'D') then begin //writeln('TryRomanToInt: Found 400'); Inc(i,2); N := N + 400; end; end ; //next max 4 C's if (i<= Len) and (S[i] = 'C') then begin //find max 4 C's //writeln('TryRomanToInt: Found 100'); Inc(i); N := N + 100; if (i<= Len) and (S[i] = 'C') then begin //writeln('TryRomanToInt: Found 100'); Inc(i); N := N + 100; end; if (i<= Len) and (S[i] = 'C') then begin //writeln('TryRomanToInt: Found 100'); Inc(i); N := N + 100; end; if (i<= Len) and (S[i] = 'C') then begin //writeln('TryRomanToInt: Found 100'); Inc(i); N := N + 100; end; end; //then XC or XL if (i + 1<= Len) and (S[i] = 'X') then begin if (S[i+1] = 'C') then begin //writeln('TryRomanToInt: Found 90'); Inc(i,2); N := N + 90; end else if (S[i+1] = 'L') then begin //writeln('TryRomanToInt: Found 40'); Inc(i,2); N := N + 40; end; end; //then L if (i<= Len) and (S[i] = 'L') then begin //writeln('TryRomanToInt: Found 50'); Inc(i); N := N + 50; end; //then (X, xx, xxx, xxxx) if (i<= Len) and (S[i] = 'X') then begin //find max 4 X's //writeln('TryRomanToInt: Found 10'); Inc(i); N := N + 10; if (i<= Len) and (S[i] = 'X') then begin //writeln('TryRomanToInt: Found 10'); Inc(i); N := N + 10; end; if (i<= Len) and (S[i] = 'X') then begin //writeln('TryRomanToInt: Found 10'); Inc(i); N := N + 10; end; if (i<= Len) and (S[i] = 'X') then begin //writeln('TryRomanToInt: Found 10'); Inc(i); N := N + 10; end; end; //then IX or IV if (i + 1<= Len) and (S[i] = 'I') then begin if (S[i+1] = 'X') then begin Terminated := (True); //writeln('TryRomanToInt: Found 9'); Inc(i,2); N := N + 9; end else if (S[i+1] = 'V') then begin Terminated := (True); //writeln('TryRomanToInt: Found 4'); Inc(i,2); N := N + 4; end; end; //then V if (not Terminated) and (i<= Len) and (S[i] = 'V') then begin //writeln('TryRomanToInt: Found 5'); Inc(i); N := N + 5; end; //then I if (not Terminated) and (i<= Len) and (S[i] = 'I') then begin Terminated := (True); //writeln('TryRomanToInt: Found 1'); Inc(i); N := N + 1; //Find max 3 closing I's if (i<= Len) and (S[i] = 'I') then begin //writeln('TryRomanToInt: Found 1'); Inc(i); N := N + 1; end; if (i<= Len) and (S[i] = 'I') then begin //writeln('TryRomanToInt: Found 1'); Inc(i); N := N + 1; end; if (i<= Len) and (S[i] = 'I') then begin //writeln('TryRomanToInt: Found 1'); Inc(i); N := N + 1; end; end; //writeln('TryRomanToInt: Len = ',Len,' i = ',i); Result := (i> Len); //if Result then writeln('TryRomanToInt: N = ',N); end; var S: String; N1, N2: Integer; B: Boolean; begin repeat write('Enter Roman numeral: '); readln(S); B := TryRomanToInt(S, N1); if B then write('TryRomanToInt(''',S,''') -> ',N1) else write('TryRomanToInt(''',S,''') FAIL'); writeln; N2 := StrUtils.RomanToInt(S); writeln('StrUtils.RomanToInt(''',S,''') = ',N2); if B and (N1 N2) then writeln('StrUtils.RomanToInt TryRomanToInt'); writeln; until S = ''; end. Bart _______________________________________________ fpc-pascal maillist - http://lists.freepascal.org/mailman/listinfo/fpc-pascal
Sven Barth
to Alberto Narduzzi
Alberto Narduzzi wrote
Ooops, I also forgot to mention that only I, X, C and M can appear up to three times in a row. V, L and D does not; they can only once.

Are you sure regarding M considering there is no symbol for 5000? Or didn't Romans count to more than 5000 - 1?

Regards,
sven

Bart
to Alberto Narduzzi: Premise: I didn't go through your entire implementation, thou' at first sight looks much…
Alberto Narduzzi wrote 5 lines
Thing is: 1. in the roman numerals, not all the digits can be subtracted from the others 2. no more than three same roman numerals can appear in a row.

My implementation is a little more relaxed as to rule 2. It is more often "violated" than rule 1.
Typically IIII is not al that uncommon.

I did an implementation some time ago, for pure fun, and provided I find it out (or maybe I can just rewrite it, on the basis of this knowledge) I will be - repository allowing ;-) - wery happy to contribute with my 2c for it.

By all means.
The princial question must be answered first though.

Bart

Bart
to Sven Barth: Are you sure regarding M considering there is no symbol for 5000? Or didn't Romans count…
Sven Barth wrote
Are you sure regarding M considering there is no symbol for 5000? Or didn't Romans count to more than 5000 - 1?

There are numerous extensions upon the scheme.
In later times adding horizontal lines above or under a Roman numeral meant multiply by 1000.

I don't believe though Romans knew negative numbers. But I'm certainly not an expert on the matter.

Bart

Frederic Da Vitoria
to Bart: [snip] Compiler doesn't like the else here. I removed the ; after the end 1 line above.…

2013/9/23 Bart

The question was: is current behaviour (accepting IIMIIC etc.) a bug or not.

What about making an option of it?

Anyhow, if the function accepts invalid combinations, what should it return? For some, the answer would be obvious (IIII), but some combinations are indeed ambiguous (IIIIM) So that we maybe could accept unambiguous invalid combinations, but I don't see how to accept ambiguous ones.

Frederic Da Vitoria (davitof) Membre de l'April - « promouvoir et défendre le logiciel libre » - http://www.april.org
Bart
to Frederic Da Vitoria
Frederic Da Vitoria wrote
What about making an option of it?

It's up to the fpc devels.

Anyhow, if the function accepts invalid combinations, what should it return? For some, the answer would be obvious (IIII), but some combinations are indeed ambiguous (IIIIM) So that we maybe could accept unambiguous invalid combinations, but I don't see how to accept ambiguous ones.

If it accepts, then it should apply the subtraction rule: only the (one) numeral left to the other (bigger one) can be subtracted. So IIIM would be III + IM = 3 + 1000-1 = 1002 (and not 1000-3 = 997) There should be no ambiguity there, it's only a pain to watch it.

Bart

Alberto Narduzzi
to Bart
Show quoted text 6 lines
> What about making an option of it? If it accepts, then it should apply the subtraction rule: only the (one) numeral left to the other (bigger one) can be subtracted. So IIIM would be III + IM = 3 + 1000-1 = 1002 (and not 1000-3 = 997) There should be no ambiguity there, it's only a pain to watch it.

as I said, roman numerals what they are.

If you want to invent something new, nowadays, extending the roman numerals to your like, well (growl!) feel free to do it, but do not pretend to call them so.

IIIM has no meaning in roman numerals. FULL STOP. There is no such logic as you depict it. 1002 = MII, and 997 = CMXCVII.
All the rest is pure bull**** in terms of roman numerals by the very name.

I begin to have a feeling that here somebody is merely talking nonsense;
say, like pretending to use the digit 2 in the binary system...

This is no decimal notation logic; things are as they are, so do not pretend to give roman numerals a value according to their position in the number itself; it is pure NONSENSE here.

Or maybe you have no clue about, so do not litter Pascal with something you don't know anything about, which will only make you just ridicule; possibly (hope not, thou') by the very Embarcadero/Delphi guys... which is not so good for Freepascal, really.

Ah, BTW, what Delphi does (as I read maybe on the very first reply to the topic, sic), here, makes no sense too. Roman numerals are what they are, irrelevantly of what somebody *may think they are*.

A.

Martin
to Alberto Narduzzi: they are not ambiguous, they are simply INVALID.
Alberto Narduzzi wrote 9 lines
> What about making an option of it? > > Anyhow, if the function accepts invalid combinations, what should it > return? For some, the answer would be obvious (IIII), but some > combinations are indeed ambiguous (IIIIM) So that we maybe could accept > unambiguous invalid combinations, but I don't see how to accept > ambiguous ones. they are not ambiguous, they are simply INVALID.

So the question is, what does or should do RomanToInt for invalid input?

It is NOT called "CheckValidRoman", so it could be that the result for invalid input is simply undefined. If it is, then all is ok.

Alberto Narduzzi
to Frederic Da Vitoria: 2013/9/23 Bart What about making an option of it? Anyhow, if the function accepts invalid…
Show quoted text 7 lines
What about making an option of it? Anyhow, if the function accepts invalid combinations, what should it return? For some, the answer would be obvious (IIII), but some combinations are indeed ambiguous (IIIIM) So that we maybe could accept unambiguous invalid combinations, but I don't see how to accept ambiguous ones.

they are not ambiguous, they are simply INVALID.

Alberto Narduzzi
to Bart: There are numerous extensions upon the scheme. In later times adding horizontal lines…
> Are you sure regarding M considering there is no symbol for 5000? Or > didn't Romans count to more than 5000 - 1?

yes I am. as 5000 - 1 would need to be written MMMMCMXCIX, which has the 4-M-in-a-row, that is invalid, as a maximum of three is allowed.

Yes, probably ancient Romans had no need to count more that 3999 Sestertiae ;-)

I don't believe though Romans knew negative numbers. But I'm certainly not an expert on the matter.

never though about, but if the only matter is DEBT, then just write positive numbers under the DEBT column, and everybody shall be happy too, provided they know the meaning of such a column ;-)

P.S.
Have no clue of roman arithmetics, thou', which looks a little more weird to implement, or just to think about :-O

A.

Alberto Narduzzi
to Martin: So the question is, what does or should do RomanToInt for invalid input? It is NOT called…
So the question is, what does or should do RomanToInt for invalid input? It is NOT called "CheckValidRoman", so it could be that the result for invalid input is simply undefined. If it is, then all is ok.

so, what does "10AT" (or "$10AT" for tht matter) return for HexToDec??? maybe NaN, if there is such a definition in Pascal??? Looks acceptable to me.

But IsValidRoman is as good as a solution too, indeed...

Bart
to Martin: So the question is, what does or should do RomanToInt for invalid input? It is NOT called…
Martin wrote
So the question is, what does or should do RomanToInt for invalid input?

Probably return 0.
Romans seemed to have no also will not break backwards compatibility, current implementatiosn returns zero if an invalid character is found (docs say otherwise though).

We could also leave things as they are and have a new TryRomanToIntStrict() function added to StrUtils.

Bart

Bart
to Alberto Narduzzi: yes I am. as 5000 - 1 would need to be written MMMMCMXCIX, which has the 4-M-in-a-row,…
Alberto Narduzzi wrote 6 lines
>> Are you sure regarding M considering there is no symbol for 5000? Or >> didn't Romans count to more than 5000 - 1? yes I am. as 5000 - 1 would need to be written MMMMCMXCIX, which has the 4-M-in-a-row, that is invalid, as a maximum of three is allowed.

It's more or less common practice to allow > 3 M's if you need values > 3999.

Bart

Bart
to Marco van de Voort: In our previous episode, Martin said: Raise an exception and have a try* version to avoid…
Marco van de Voort wrote
Raise an exception

This will break existing implementations that either expect IM to be 999, or ABC to be 0 (indicating failure), and that certainly do not expect that this function throws an exception at them.

and have a try* version to avoid the exception, just like all other str*to<numeric> functions.

You beat me by a few minutes ....

Bart

Marco van de Voort
to Bart

In our previous episode, Bart said:

Show quoted text 5 lines
> Raise an exception This will break existing implementations that either expect IM to be 999, or ABC to be 0 (indicating failure), and that certainly do not expect that this function throws an , but since the routine probably has low utilisation I choose for

structuring all conversion routines all the same.

Moreover I don't think that first attempts should fixate interfaces and behaviour forever.

And let's not beat about the bush: the main reason for the routine is to show beginning programmers that their homework can be done using a fixed function :-)

Reinier Olislagers
to Bart: Probably return 0. Romans seemed to have no also will not break backwards compatibility,…
Bart wrote 9 lines
On 9/23/13, Martin wrote: > So the question is, what does or should do RomanToInt for invalid input? Probably return 0. Romans seemed to have no zero at all. It also will not break backwards compatibility, current implementatiosn returns zero if an invalid character is found (docs say otherwise though).

Yes, I agree that returning 0 is a good solution. The docs can be changed.

We could also leave things as they are and have a new TryRomanToIntStrict() function added to StrUtils.

As there are no issues with compatibility that would IMO only make the solution needlessly more complex.

Reinier Olislagers
to Marco van de Voort: In our previous episode, Bart said: structuring all conversion routines all the same.…
Marco van de Voort wrote 9 lines
In our previous episode, Bart said: >> Raise an exception > > This will break existing implementations that either expect IM to be > 999, or ABC to be 0 (indicating failure), and that certainly do not > expect that this function throws an exception at them. Yes, but since the routine probably has low utilisation I choose for structuring all conversion routines all the same.

I would rather choose for maintaining backward compatiblity, the *de facto behaviour* (return 0 on invalid values) as it is quite sensible for this kind of numbers.

Moreover I don't think that first attempts should fixate interfaces and behaviour forever.

It's quite strange though that Delphi compatibility is quite insistently adhered to (BTW a good decision IMO).
The function has been in FPC stable (for presumably a while?) so I would really think hard before changing it.

And let's not beat about the bush: the main reason for the routine is to show beginning programmers that their homework can be done using a fixed function :-)

Nice going to show beginning programmers how to break backward compatibility then ;)

However, this is all just me 2c so it's up to the FPC devs to decide....

Regards,
Reinier

Reinier Olislagers
to Alberto Narduzzi: yes I am. as 5000 - 1 would need to be written MMMMCMXCIX, which has the 4-M-in-a-row,…
Alberto Narduzzi wrote
> I don't believe though Romans knew negative numbers.

Well, they could subtract, couldn't they :)

Show quoted text 5 lines
> But I'm certainly not an expert on the matter. never though about, but if the only matter is DEBT, then just write positive numbers under the DEBT column, and everybody shall be happy too, provided they know the meaning of such a column ;-)

You just described an alternative indication of a negative number... only instead of a minus sign, you use column position as an indicator ;) To calculate net equity, you'd still have to subtract the debts from the possessions, i.e. deal with the debt as negative numbers.

P.S. Have no clue of roman arithmetics, thou', which looks a little more weird to implement, or just to think about :-O

I would be very careful in definining too many restrictions on what you accept as Roman numerals as the number of fixed rules is rather small and there are a lot of exceptions, especially in later years (i.e. Bart's example of bigger numbers).
In other words, the system of Roman numerals was not fixed; it changed over time, it was mostly based on conventions that fell short of actual rules.

Frederic Da Vitoria
to Reinier Olislagers: I would rather choose for maintaining backward compatiblity, the *de facto behaviour*…

2013/9/24 Reinier Olislagers

Show quoted text 21 lines
On 24/09/2013 09:09, Marco van de Voort wrote: > In our previous episode, Bart said: >>> Raise an exception >> >> This will break existing implementations that either expect IM to be >> 999, or ABC to be 0 (indicating failure), and that certainly do not >> expect that this function throws an exception at them. > > Yes, but since the routine probably has low utilisation I choose for > structuring all conversion routines all the same. I would rather choose for maintaining backward compatiblity, the *de facto behaviour* (return 0 on invalid values) as it is quite sensible for this kind of numbers. > Moreover I don't think that first attempts should fixate interfaces > and behaviour forever. It's quite strange though that Delphi compatibility is quite insistently adhered to (BTW a good decision IMO). The function has been in FPC stable (for presumably a while?) so I would really think hard before changing it.

I could not find any trace of it on the embarcadero web site. Wouldn't we be taking into account a compatibility with something which does not exist, by any chance?

Show quoted text 6 lines
And let's not beat about the bush: the main reason for the routine is to > show beginning programmers that their homework can be done using a fixed > function :-) Nice going to show beginning programmers how to break backward compatibility then ;)

Well, the alternative would be showing how to maintain buggy implementations because you don't want to break backward compatibility :-) Or it could show that sometimes rules have to be infringed. Or it could show the merits of exploring thoroughly the limits of a problem before committing code which could later place you in such a conflicting situation (I am not throwing stones to the developer who committed this function first, I have too often done the same mistake myself)

Frederic Da Vitoria (davitof) Membre de l'April - « promouvoir et défendre le logiciel libre » - http://www.april.org
Frederic Da Vitoria
to Reinier Olislagers: Well, they could subtract, couldn't they :) You just described an alternative indication…

2013/9/24 Reinier Olislagers

Show quoted text 14 lines
On 23/09/2013 23:34, Alberto Narduzzi wrote: >> I don't believe though Romans knew negative numbers. Well, they could subtract, couldn't they :) >> But I'm certainly not an expert on the matter. > > never though about, but if the only matter is DEBT, then just write > positive numbers under the DEBT column, and everybody shall be happy > too, provided they know the meaning of such a column ;-) You just described an alternative indication of a negative number... only instead of a minus sign, you use column position as an indicator ;) To calculate net equity, you'd still have to subtract the debts from the possessions, i.e. deal with the debt as negative numbers.

Not quite. The mathematical result would be the same, but try explaining what a negative number is to a kid :-) You may have forgotten it, but there was a time when you had to be explained that a - b = a + (-b) (which is not exactly using a negative number but closely related to it). In the above example, the debt is to be subtracted, this does not automatically trigger the abstract concept of negative number in the user's mind. I even think that if the romans really had the concept of negative numbers, they would have devised a notation for it allowing them to include them in texts. Does such a notation exist?

Show quoted text 11 lines
P.S. Have no clue of roman arithmetics, thou', which looks a little > more weird to implement, or just to think about :-O I would be very careful in definining too many restrictions on what you accept as Roman numerals as the number of fixed rules is rather small and there are a lot of exceptions, especially in later years (i.e. Bart's example of bigger numbers). In other words, the system of Roman numerals was not fixed; it changed over time, it was mostly based on conventions that fell short of actual rules.

You are probably right: see
http://www.web40571.clarahost.co.uk/roman/howtheywork.htm

Frederic Da Vitoria (davitof) Membre de l'April - « promouvoir et défendre le logiciel libre » - http://www.april.org
Marco van de Voort
to Reinier Olislagers: I would rather choose for maintaining backward compatiblity, the *de facto behaviour*…

In our previous episode, Reinier Olislagers said:

Show quoted text 6 lines
> > Yes, but since the routine probably has low utilisation I choose for > structuring all conversion routines all the same. I would rather choose for maintaining backward compatiblity, the *de facto behaviour* (return 0 on invalid values) as it is quite sensible for this kind of numbers.

It is non-orthogonal.

Show quoted text 5 lines
> Moreover I don't think that first attempts should fixate interfaces > and behaviour forever. It's quite strange though that Delphi compatibility is quite insistently adhered to (BTW a good decision IMO).

Because that has an use. The internal FPC compatability, specially in the more fringe areas like this, service no use than fattening maillist archives IMHO.

Sven Barth
to Frederic Da Vitoria: 2013/9/24 Reinier Olislagers I could not find any trace of it on the embarcadero web…
Frederic Da Vitoria wrote 31 lines
2013/9/24 Reinier Olislagers < > On 24/09/2013 09:09, Marco van de Voort wrote: > In our previous episode, Bart said: >>> Raise an exception >> >> This will break existing implementations that either expect IM to be >> 999, or ABC to be 0 (indicating failure), and that certainly do not >> expect that this function throws an exception at them. > > Yes, but since the routine probably has low utilisation I choose for > structuring all conversion routines all the same. I would rather choose for maintaining backward compatiblity, the *de facto behaviour* (return 0 on invalid values) as it is quite sensible for this kind of numbers. > Moreover I don't think that first attempts should fixate interfaces > and behaviour forever. It's quite strange though that Delphi compatibility is quite insistently adhered to (BTW a good decision IMO). The function has been in FPC stable (for presumably a while?) so I would really think hard before changing it. I could not find any trace of it on the embarcadero web site. Wouldn't we be taking into account a compatibility with something which does not exist, by any chance?

When there is no Delphi-compatibility needed there is still backwards compatibility to code developed in FPC.

Regards,
Sven

Reinier Olislagers
to Frederic Da Vitoria: 2013/9/24 Reinier Olislagers I could not find any trace of it on the embarcadero web…
Frederic Da Vitoria wrote 16 lines
2013/9/24 Reinier Olislagers < > On 24/09/2013 09:09, Marco van de Voort wrote: > In our previous episode, Bart said: > Moreover I don't think that first attempts should fixate interfaces > and behaviour forever. It's quite strange though that Delphi compatibility is quite insistently adhered to (BTW a good decision IMO). The function has been in FPC stable (for presumably a while?) so I would really think hard before changing it. I could not find any trace of it on the embarcadero web site. Wouldn't we be taking into account a compatibility with something which does not exist, by any chance?

Ave Frederice ;)

Just to be clear: I'm talking about backward compatibility with FPC code. I think Bart is, too.
I haven't seen found any Delphi equivalent for the function either.

Show quoted text 7 lines
Nice going to show beginning programmers how to break backward compatibility then ;) Well, the alternative would be showing how to maintain buggy implementations because you don't want to break backward compatibility :-)

Nice one ;)
.. and TBH, breaking implementations is sometimes necesary. However, see below for why I don't think the returning 0 part is buggy.

Show quoted text 5 lines
Or it could show that sometimes rules have to be infringed. Or it could show the merits of exploring thoroughly the limits of a problem before committing code which could later place you in such a conflicting situation (I am not throwing stones to the developer who committed this function first, I have too often done the same mistake myself)

Remember though that we're talking about choosing between: If invalid [2] input is found:
1. returning 0 - as the function already does, it's just not documented as such.
2. raising an exception like a lot of other conversion functions

Keeping in mind that there is no Roman number symbol for zero [1], option 1 seems quite valid and obvious.

I don't deeply care which choice is made. It's just that I don't think the additional work and possible impact on changing code outweigh the benefits. Others obviously disagree, fine ;)

[1] Unless, yes, another exception, you think of Bede's use of N... [2] Ignoring the discussion about what's invalid as that has little to doe with the choice mentioned here.

Reinier Olislagers
to Marco van de Voort: In our previous episode, Reinier Olislagers said: It is non-orthogonal. Because that has…
Marco van de Voort wrote 9 lines
In our previous episode, Reinier Olislagers said: >> >> Yes, but since the routine probably has low utilisation I choose for >> structuring all conversion routines all the same. > I would rather choose for maintaining backward compatiblity, the *de > facto behaviour* (return 0 on invalid values) as it is quite sensible > for this kind of numbers. It is non-orthogonal.

What is non-orthogonal? I'm indicating that I value backward compatiblity higher than breaking compatibility to match existing structures. I also indicate why this compatiblity is not such a bad decision in the first place.
I have a bit of trouble understanding what you mean by "it's non-orthogonal"

Show quoted text 7 lines
>> Moreover I don't think that first attempts should fixate interfaces >> and behaviour forever. > It's quite strange though that Delphi compatibility is quite insistently > adhered to (BTW a good decision IMO). Because that has an use. The internal FPC compatability, specially in the more fringe areas like this, service no use than fattening maillist archives IMHO.

If you don't see a use for backward compatibility for existing code... let's just say I'll stop fattening maillist archives right now.

Lukasz Sokol
to Bart: [snip] Compiler doesn't like the else here. I removed the ; after the end 1 line above.…
Bart wrote 14 lines
On 9/23/13, Lukasz Sokol wrote: > function TryRomanToInt(AInput:String; out AResult: integer):boolean; > var i, Len, N, Np1 : integer; [snip] > > > if N >= Np1 then AResult := AResult + N > else AResult := AResult - N; > > end; > else // i = Len-1 = last char we just add (consider : MCM : add 1000, Compiler doesn't like the else here. I removed the ; after the end 1 line above.

I said below it's only mind-compiled ;)

Show quoted text 23 lines
> sub 100, add 1000 = 1900) > begin > AResult := AResult + N; > end; > end; // for > // if the above, after all characters are through, has resulted in 0 or > less, > // we invalidate everything at the end (consider : CMLM, IIIM ) > > Result := AResult > 0; > if not Result then AResult := 0; > end; > > (only mind-compiled ;) tests welcome ;) ) > It produces "odd" output though: C:\Users\Bart\LazarusProjecten\ConsoleProjecten>test Enter Roman numeral: LL //should be illegal TryRomanToInt('LL') -> 50 StrUtils.RomanToInt('LL') = 100

Oh I see the loop needs to run from 0 to Len-1 ?.... it missed the last (or first) char

Enter Roman numeral: MM TryRomanToInt('MM') -> 1050 //obviously should be 2000
But this looks like you're adding the results? Or are you reusing the
same variable ? In that case stick AResult := 0 at the top of the function please
StrUtils.RomanToInt('MM') = 2000 Enter Roman numeral: II TryRomanToInt('II') -> 1051 // nice one!

Yeah totally added to former Aresult, and missed last char ...

StrUtils.RomanToInt('II') = 2 So, it's a little bit flawed.

:)

function TryRomanToInt(AInput:String; out AResult: integer):boolean;
var i, Len, N, Np1 : integer;
  function TryRomanCharToInt(AIn: Char; out AOut: integer): boolean;
  begin
    case AIn of
      'M' : AOut := 1000;
      'D' : AOut := 500;
      'C' : AOut := 100;
      'L' : AOut := 50;
      'X' : AOut := 10;
      'V' : AOut := 5;
      'I' : AOut := 1;
    else
      AOut := 0;
    end;
    Result := (AOut > 0);
  end;
begin
  // if it looks like shameless c&p, it's because it is ;)
  Result := False;
  AResult := 0; // << here you've reused the variable passed to AResult somewhere
  AInput := UpperCase(AInput);  //don't use AnsiUpperCase please
  Len := Length(AInput);
  if (Len = 0) then Exit;
  //
  i := 1;
  for i := 0 to Len-1 do // which char am I actually missing here???
  begin
    if not TryRomanCharToInt(AInput[i], N) then
    begin
      AResult := -1; // invalidate everything
      exit;
    // writeln('Not a valid roman numeral at position ',i);
    end;
    if (i > Len-1) then
      begin
        if not TryRomanCharToInt(AInput[i+1], Np1) then
        begin
          AResult := -1; // invalidate everithing
          exit;
        // writeln('Not a valid roman numeral at position ',i+1);
        end;
	// according to wikipedia, .
        if not (((N = 100) and (Np1 > 100)) or  // C can be placed before L or M
                ((N = 10) and (Np1 > 10) and (Np1 <= 100)) or // X can be placed before L or C
                ((N = 1) and (Np1 > 1) and (Np1 <= 50)))  // I can be placed before V and X << here too
        then
          begin
            AResult := -1; // invalidate everithing: catches MDCLXVIVXLDM
            exit;
            // writeln('Not a valid roman numeral combination at position ',i, ' and ',i+1);
          end;
        if N >= Np1 then AResult := AResult + N
        else AResult := AResult - N;
      end // i > Len-1, you're right about the ;
    else // i = Len-1 = last char we just add (consider : MCM : add 1000, sub 100, add 1000 = 1900)
     begin // alternatively this should exit the loop maybe on the N-1th character
       AResult := AResult + N;
     end;
  end; // for
  // if the above, after all characters are through, has resulted in 0 or less,
  // we invalidate everything at the end (consider : CMLM, IIIM )
  Result := AResult > 0;
  if not Result then AResult := 0;
end;
Show quoted text 9 lines
Anyhow, let's not focus upon what the new code should be. The question was: is current behaviour (accepting IIMIIC etc.) a bug or not. Bart _______________________________________________ fpc-pascal maillist - http://lists.freepascal.org/mailman/listinfo/fpc-pascal
Sven Barth
to Reinier Olislagers: What is non-orthogonal? I'm indicating that I value backward compatiblity higher than…
Reinier Olislagers wrote 13 lines
On 24/09/2013 11:11, Marco van de Voort wrote: > In our previous episode, Reinier Olislagers said: >>> Yes, but since the routine probably has low utilisation I choose for >>> structuring all conversion routines all the same. >> I would rather choose for maintaining backward compatiblity, the *de >> facto behaviour* (return 0 on invalid values) as it is quite sensible >> for this kind of numbers. > It is non-orthogonal. What is non-orthogonal? I'm indicating that I value backward compatiblity higher than breaking compatibility to match existing structures. I also indicate why this compatiblity is not such a bad decision in the first place. I have a bit of trouble understanding what you mean by "it's non-orthogonal"

Non-orthogonal means in this case that RomanToInt behaves different than e.g. StrToInt.

Regards,
Sven

DaWorm
to Sven Barth

Just a guess here, but I would think there have now been more posts in this thread than the total number of programs to use this function. Probably by a wide margin.

Reinier Olislagers
to Sven Barth: Non-orthogonal means in this case that RomanToInt behaves different than e.g. StrToInt.…
Sven Barth wrote 17 lines
Am 24.09.2013 11:27, schrieb Reinier Olislagers: > On 24/09/2013 11:11, Marco van de Voort wrote: >> In our previous episode, Reinier Olislagers said: >>>> Yes, but since the routine probably has low utilisation I choose for >>>> structuring all conversion routines all the same. >>> I would rather choose for maintaining backward compatiblity, the *de >>> facto behaviour* (return 0 on invalid values) as it is quite sensible >>> for this kind of numbers. >> It is non-orthogonal. > What is non-orthogonal? I'm indicating that I value backward > compatiblity higher than breaking compatibility to match existing > structures. I also indicate why this compatiblity is not such a bad > decision in the first place. > I have a bit of trouble understanding what you mean by "it's > non-orthogonal" Non-orthogonal means in this case that RomanToInt behaves different than e.g. StrToInt.

Sorry, but I'd rather hear that from Marco himself. Your explanation doesn't make sense either; IMO it was sufficiently clear in the discussion that we all agree that RomanToInt's behaviour is different from many/all other conversion routines.

Sven Barth
to Reinier Olislagers
Reinier Olislagers wrote 22 lines
On 24/09/2013 13:13, Sven Barth wrote: > Am 24.09.2013 11:27, schrieb Reinier Olislagers: >> On 24/09/2013 11:11, Marco van de Voort wrote: >>> In our previous episode, Reinier Olislagers said: >>>>> Yes, but since the routine probably has low utilisation I choose for >>>>> structuring all conversion routines all the same. >>>> I would rather choose for maintaining backward compatiblity, the *de >>>> facto behaviour* (return 0 on invalid values) as it is quite sensible >>>> for this kind of numbers. >>> It is non-orthogonal. >> What is non-orthogonal? I'm indicating that I value backward >> compatiblity higher than breaking compatibility to match existing >> structures. I also indicate why this compatiblity is not such a bad >> decision in the first place. >> I have a bit of trouble understanding what you mean by "it's >> non-orthogonal" > Non-orthogonal means in this case that RomanToInt behaves different than > e.g. StrToInt. Sorry, but I'd rather hear that from Marco himself. Your explanation doesn't make sense either; IMO it was sufficiently clear in the discussion that we all agree that RomanToInt's behaviour is different from many/all other conversion routines.

You want to hear it from Marco? Here:

Yes, but since the routine probably has low utilisation I choose for structuring all conversion routines all the same.

Regards,
Sven

Marco van de Voort
to Reinier Olislagers: What is non-orthogonal? I'm indicating that I value backward compatiblity higher than…

In our previous episode, Reinier Olislagers said:

Show quoted text 5 lines
>> facto behaviour* (return 0 on invalid values) as it is quite sensible >> for this kind of numbers. > > It is non-orthogonal. What is non-orthogonal?

RomanToInt uses 0 to signal error, other xxxtoint functions throw exceptions.

Show quoted text 5 lines
> Because that has an use. The internal FPC compatability, specially in > the more fringe areas like this, service no use than fattening maillist > archives IMHO. If you don't see a use for backward compatibility for existing code...

I see it only as an starting point, not as a rigid unbendable rule.

Reinier Olislagers
to Sven Barth: You want to hear it from Marco? Here: Sven
Sven Barth wrote 24 lines
Am 24.09.2013 13:47, schrieb Reinier Olislagers: > On 24/09/2013 13:13, Sven Barth wrote: >> Am 24.09.2013 11:27, schrieb Reinier Olislagers: >>> On 24/09/2013 11:11, Marco van de Voort wrote: >>>> In our previous episode, Reinier Olislagers said: >>>>>> Yes, but since the routine probably has low utilisation I choose for >>>>>> structuring all conversion routines all the same. >>>>> I would rather choose for maintaining backward compatiblity, the *de >>>>> facto behaviour* (return 0 on invalid values) as it is quite sensible >>>>> for this kind of numbers. >>>> It is non-orthogonal. >>> What is non-orthogonal? I'm indicating that I value backward >>> compatiblity higher than breaking compatibility to match existing >>> structures. I also indicate why this compatiblity is not such a bad >>> decision in the first place. >>> I have a bit of trouble understanding what you mean by "it's >>> non-orthogonal" >> Non-orthogonal means in this case that RomanToInt behaves different than >> e.g. StrToInt. > Sorry, but I'd rather hear that from Marco himself. > Your explanation doesn't make sense either; IMO it was sufficiently > clear in the discussion that we all agree that RomanToInt's behaviour is > different from many/all other conversion routines. You want to hear it from Marco? Here:

<snip earlier quote>

Depends on what he meant by "it" and "non-orthogonal", doesn't it? I had trouble believing Marco thought just repeating his point about the function not fitting in with the rest of the conversion functions would be any use - especially because we both agreed about that point.

Now it seems Marco cannot appreciate that I was discussing weighing various arguments pro and con changing the function, and was just stating and maintaining a black and white position that looks extremely odd to me ("backward compatibility is irrelevant"). That's why I asked him what he meant.

All in all, this *is* really getting useless. I'll leave this thread for what it is. I think everything that could usefully have been said has already been said.

Reinier Olislagers
to Marco van de Voort: In our previous episode, Reinier Olislagers said: RomanToInt uses 0 to signal error,…
Marco van de Voort wrote
In our previous episode, Reinier Olislagers said:

<snip>

Show quoted text 8 lines
>> Because that has an use. The internal FPC compatability, specially in >> the more fringe areas like this, service no use than fattening maillist >> archives IMHO. > > If you don't see a use for backward compatibility for existing code... I see it only as an starting point, not as a rigid unbendable rule. _

There is just *no* way the second and the first statements can possibly match.
Furthermore you ignore any nuanced response I post and just repeat some point you stated earlier.
Yes, I suppose it's an exchange of views but it is not useful at all.

Bart
to Reinier Olislagers

My 2 cents on the "backwards compatibility" discussion.

First: backwards compatibility must be broken if the old implementation is wrong. This seems obvious, but I have had discussions about that in the past when I corrected another conversion routine in fpc.
Wether or not the current implenetation is wrong, is open for discussion.

Breaking backwards compatibility for consistency with similar RTL behaviour is IMO not a bad thing. We have done so in th epast for Lazarus as well.
As said before: given the expected number of users of this function, I weould consider doing it.
For consistency we should also have a TryRomanToInt(S: String; out Value: Integer): Boolean; function.

As stated before: Delphi compatibility does not seem to be apply here.

My 2 cents on the "strictness" of the parsing.

Since there are various, more or less accepted, "rules", a "strictness" parameter could be added as an optional parameter to the function.
It does not break consistency with other conversion functions, since they are hardly open for interpretation, and so they don't need such a thing.

Strictness modes could be:
- Strict: most strict set of rules (it does not accept numbers > 3999, nor more than 3 repeats of the same numeral)
- Relaxed: allows unlimited number of starting "M", and allows up to 4 repeats of C, X and I.
- DontCare: like current behaviour.

Bart

Alberto Narduzzi
to Bart
Show quoted text 15 lines
My 2 cents on the "strictness" of the parsing. Since there are various, more or less accepted, "rules", a "strictness" parameter could be added as an optional parameter to the function. It does not break consistency with other conversion functions, since they are hardly open for interpretation, and so they don't need such a thing. Strictness modes could be: - Strict: most strict set of rules (it does not accept numbers> 3999, nor more than 3 repeats of the same numeral) - Relaxed: allows unlimited number of starting "M", and allows up to 4 repeats of C, X and I. - DontCare: like current behaviour.

Now, this is a very nice and clean approach to the problem, together with an equally nice and clean solution.

Cheers, A.