What happened to the built in CARD() function? lazarus / 64-FPC development ---------------------------------------------------------------------- meteoricshipyards · 2016-01-12 22:29 Way back in the late 70's and early 80's I worked with Pascal on a CDC NOS machine.  This past Summer I recently found a listing of an Adventure game that I played way back then.  I researched, found Lazarus and Free Pascal, and started typing it in.  One thing that didn't work was the CARD() function.  For those of you who don't know it, it returns the number of items in a set variable.  (For really obscure trivia, it was implemented as a single instruction on the CDC 60 bit architecture).  As I only had one type of set that I needed the function for, I coded up my own.  But I was wondering when and why it disappeared, and if I should be aware of other items that may have gone away. Tom A. Planning on "publishing" the game on an Interactive Fiction site in within a month. ---------------------------------------------------------------------- molly · 2016-01-13 02:25 Quote from: meteoricshipyards on January 12, 2016, 11:29:59 pmOne thing that didn't work was the CARD() function. Perhaps you might find this thread interesting. ---------------------------------------------------------------------- marcov · 2016-01-13 08:17 It never was in Turbo Pascal, so if it was standard, it already fell off before. It is also possible it was just some intrinsic for a CDC instruction and not standard.  As Molly correctly pointed out, FPC has popcount, but it is only for integers, so only works for sets the size of a register. ---------------------------------------------------------------------- Thaddy · 2016-01-13 11:21 @Marco: You yourself suggested PopCnt sometime ago.... http://stackoverflow.com/questions/12934017/knowing-how-many-elements- a-set-have If that is correct and there are already compiler iintrinsics, it shouldn't be too hard to massage the compiler into having a card(). ---------------------------------------------------------------------- marcov · 2016-01-13 11:46 My point is mainly that if an ancient compiler had it, it is not necessarily standard. But I guess nothing stops you from submitting a patch if it is easy. ---------------------------------------------------------------------- meteoricshipyards · 2016-01-13 19:01 It was in the User Manual and Report at the time, and was the first or second implementation of Pascal, so it was pretty standard :-) But it is not a function that is used often, or needs to be part of the standard, as far as I'm concerned.  Other functions that are gone (and were definitely implementation specific) are the eos(file) (end of segment) and linelimit(text) functions. But I'm just glad I found this.  I consider Pascal my native language (even though I learned BASIC and FORTRAN before it). Tom A. Now to figure out objects in Pascal.... ---------------------------------------------------------------------- ArtLogi · 2016-01-17 01:18 Well it seems to be in the ISO/IEC 10206:1 Page X, under "Set Extensions" QuoteThe function card yields the number of members in a set. http://pascal-central.com/docs/iso10206.pdf http://pascal-central.com/standards.html  Interesting I had an image that there were only one standard along the years considering pascal language. 1982 and no further updates and standardization work. ---------------------------------------------------------------------- marcov · 2016-01-17 15:55 Don't get me wrong, CARD() would be fine, as would other work on sets - like lifting the upper limit of 256 elements. (this makes using enums and sets difficult since it bounds itsscaling) - respecting the lower limit (too often, elements are allocated starting with 0, even if the lower bound is not 0 or close to it) - not even talking of the sparse sets in some Pascal dialects etc. ---------------------------------------------------------------------- Bart · 2016-01-17 16:09 Extending Length() to handle sets would, with regard to the name of the function, make more sense to me. Bart ---------------------------------------------------------------------- Thaddy · 2018-02-10 08:36 Old thread but not much has happened. Summary: For small sets (max 32 members) you can use PopCnt For large sets (max 256 members you can also use PopCnt, but over an array. I dreamed up this today because I only needed it now. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function Card(const s;SmallSet:boolean =true):word;inline;var  t:packed array [0..3] of qword absolute s;begin  if SmallSet then     Result := PopCnt(Dword(s))   else    Result := PopCnt(T[0])+PopCnt(T[1])+PopCnt(T[2])+PopCnt(T[3]);end; Now if somebody knows a nifty trick to obtain the set size from an untyped const, I can get rid of the second parameter. In extended ISO Pascal this is solved by an any_set intrinsic ---------------------------------------------------------------------- taazz · 2018-02-10 09:19 Quote from: Thaddy on February 10, 2018, 09:36:45 amNow if somebody knows a nifty trick to obtain the set size from an untyped const, I can get rid of the second parameter. how about making the second parameter the size of the set instead of a boolean. ---------------------------------------------------------------------- Thaddy · 2018-02-10 10:11 Quote from: taazz on February 10, 2018, 10:19:04 amhow about making the second parameter the size of the set instead of a boolean. That's what I had first but I want to get rid of the parameter altogether. Another option was two separate functions. (Can't overload them). I tried rtti but that fails on tkUnKnown/$formal ---------------------------------------------------------------------- marcov · 2018-02-10 13:07 Quote from: Thaddy on February 10, 2018, 09:36:45 amNow if somebody knows a nifty trick to obtain the set size from an untyped const, I can get rid of the second parameter. Well, there is also a problem that you need to mask out unused bits. They are probably zero, but I don't know how reliable that is. And sets are multiples of bytes, not qwords. ---------------------------------------------------------------------- Thaddy · 2018-02-10 14:33 @marcov The unused bits in sets < 33 bits are predictably zero, as are the unused bytes in a large up-to-32 byte set (this is also documented) I can not use, however, just the function body for large sets for any set because that fails on stack-allocated space. And I tested that that will fail. (of course, but I tested it) The use of qwords is an internal and legal for any bit set > 32 bits. It merely uses the most efficient available PopCnt overload. ---------------------------------------------------------------------- howardpc · 2018-02-10 15:16 Quote from: Thaddy on February 10, 2018, 09:36:45 amNow if somebody knows a nifty trick to obtain the set size from an untyped const, I can get rid of the second parameter. An untyped const parameter has no type information (SizeOf() = 0, TTypeKind = tkUnknown), which is logical since it is untyped. You can, however, determine the size predicated on the unused bits being zero. Where is this documented? The following routine may not be very efficient, but it expresses the cardinality of any set passed as an untyped parameter to the Card() function. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program Cardinality; {$Mode objfpc}{$H+} type  T4ByteRange      = Byte(0)..Byte(3);  T32ByteRange     = Byte(0)..Byte(31);  TMediumSetQWords = array[T4ByteRange] of QWord; function Card(const aSet): Integer;var  msqw: TMediumSetQWords absolute aSet;  w: Word = 0;  b: Byte;begin  for b := Low(T4ByteRange) to High(T4ByteRange) do  Inc(w, PopCnt(msqw[b]));  if (w = 0) then    Exit(0);  Result := 0;  for b:= 1 to High(T4ByteRange) do    Inc(Result);  if (Result = 0) then    Exit(PopCnt(DWord(aSet)))  else Exit(w);end; var  smallSet:  set of T32ByteRange;  mediumSet: set of Char;  c:         Char;  b:       T32ByteRange; begin  smallSet := [];    WriteLn('smallSet: Card([]) = ', Card(smallSet));  smallSet := [31];    WriteLn('smallSet: Card([31]) = ', Card(smallSet));  smallSet := [1,3,5,7,31];    WriteLn('smallSet: Card([1,3,5,7,31]) = ', Card(smallSet));  for b := Low(T32ByteRange) to High(T32ByteRange) do    Include(smallSet, b);    WriteLn('smallSet: Card([full small set]) = ', Card(smallSet));  WriteLn;   mediumSet := [];    WriteLn('mediumSet: Card([]) = ',Card(mediumSet));  mediumSet := ['?'];    WriteLn('mediumSet: Card([''?'']) = ',Card(mediumSet));  mediumSet := ['!','f','%','D'];    WriteLn('mediumSet: Card([''!'',''f'',''%'',''D'']) = ',Card(mediumSet));  Exclude(mediumSet, 'f');    WriteLn('mediumSet: after Exclude(mediumSet,''f''), Card(mediumSet) = ',Card(mediumSet));  for c in Char do    Include(mediumSet, c);  WriteLn('Card([full medium set]) = ',Card(mediumSet));   ReadLn;end. ---------------------------------------------------------------------- marcov · 2018-02-11 12:09 Quote from: Thaddy on February 10, 2018, 03:33:53 pmThe use of qwords is an internal and legal for any bit set > 32 bits. It merely uses the most efficient available PopCnt overload. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {$mode delphi} type  txxx = set of 0..247; begin  writeln(sizeof(txxx));end. Prints 31. ---------------------------------------------------------------------- Thaddy · 2018-02-11 12:43 Quote from: marcov on February 11, 2018, 01:09:55 pmPrints 31. https://www.freepascal.org/docs-html/ref/refsu16.html#x40-580003.3.3 " The compiler stores small sets (less than 32 elements) in a Longint, if the type range allows it. This allows for faster processing and decreases program size. Otherwise, sets are stored in 32 bytes." The docs are wrong anyway since it should read less than 33 bits since set of 0..31 is size 4. Maybe file a bug report against documentation? ---------------------------------------------------------------------- marcov · 2018-02-11 12:49 Quote from: Thaddy on February 11, 2018, 01:43:13 pmhttps://www.freepascal.org/docs-html/ref/refsu16.html#x40-580003.3.3 " The compiler stores small sets (less than 32 elements) in a Longint, if the type range allows it. This allows for faster processing and decreases program size. Otherwise, sets are stored in 32 bytes." True for mode objfpc, but not for delphi.  IIRC before the bytewise growing, Delphi mode already had a while that sets were multiples of 4 (might still be on non x86). That might go back as far as 2.2 documentation bug filed: https://bugs.freepascal.org/view.php?id=33156 ---------------------------------------------------------------------- ASerge · 2018-02-11 13:04 Quote from: Thaddy on February 11, 2018, 01:43:13 pm" The compiler stores small sets (less than 32 elements) in a Longint, if the type range allows it. This allows for faster processing and decreases program size. Otherwise, sets are stored in 32 bytes." I think the documentation is inaccurate and most likely in FPC, as in Delphi: Max div 8 - Min div 8 + 1. ---------------------------------------------------------------------- howardpc · 2018-02-11 13:11 After a bit more thought, a simplified Card() is this: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {$Mode objfpc} type   TMediumSetDWords = array[0..7] of DWord; function Card(const aSet): Word;var  msdw: TMediumSetDWords absolute aSet;  b: Byte;begin  Result := PopCnt(msdw[0]);  for b := 1 to 7 do    Inc(Result, PopCnt(msdw[b]));end; ---------------------------------------------------------------------- Thaddy · 2018-02-11 13:46 Quote from: howardpc on February 11, 2018, 02:11:20 pmAfter a bit more thought, a simplified Card() is this: - That code is less efficient on modern processors since PopCnt has a qword option (like I used) - It will - just like my own code - fail when called on a set that is stack allocated, i.e. from within a procedure or function. Unused bytes are "dirty". - And as Marco demonstrates it will always fail in {$mode delphi}. The unused bytes are undefined, i.e. also dirty. Without a means of determining the size of the set inside a function and from an un-typed const it seems impossible to write a typesafe generic card function as it stands using high-level code. For small sets it works though: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i // card for sets with 32 elements or less.function card(const s):integer;begin  card := PopCnt(Dword(s));end; For the compiler it should not be any problem to introduce such a feature since it knows the size of the set at compile time. This looks only solvable with compiler magic. (Just like PopCnt) ---------------------------------------------------------------------- PascalDragon · 2018-02-17 18:32 Generic functions to the rescue: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program tcard; {$mode objfpc} generic function Card(aSetValue: T): LongInt;var  i: LongInt;begin  if SizeOf(aSetValue) <= 4 then    Result := PopCnt(PLongWord(@aSetValue)^)  else begin    Result := 0;  for i := 0 to SizeOf(aSetValue) div SizeOf(LongWord) - 1 do      Inc(Result, PopCnt(PLongWord(@aSetValue)[i]));  end;end; type  TSet1 = set of 1..3;  TSet2 = set of 1..40; var  set1: TSet1;  set2: TSet2;begin  set1 := [2];  set2 := [4, 9, 23, 40];   Writeln(specialize Card(set1));  Writeln(specialize Card(set2));end.  Sadly the "Unreachable code" warning can not be suppressed ---------------------------------------------------------------------- ASerge · 2018-02-17 20:28 Quote from: PascalDragon on February 17, 2018, 07:32:02 pmSadly the "Unreachable code" warning can not be suppressed  1. This is compiled only in the development version. 2. With {$mode objfpc} only two set sizes: 4 and 32. Thus, either one or the other part of "if" is an unreachable code. 3. With {$mode delphi} this code, with little modification, is wrong, because, as @Thaddy say "The unused bytes are undefined, i.e. also dirty". Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program Project1;{$APPTYPE CONSOLE} {-$mode delphi}{$mode objfpc} {$IFDEF FPC_OBJFPC}generic{$ENDIF}function Card(const aSetValue: T): Integer;var  i: Integer;begin  Result := 0;  for i := 0 to SizeOf(T) - 1 do    Inc(Result, PopCnt(PByte(@aSetValue)[i]));end; type  TSet1 = set of 0..3;  TSet2 = set of 0..40; var  set1: TSet1 = [2];  set2: TSet2 = [4, 9, 23, 40];begin  set1 := [2];  set2 := [4, 9, 23, 40];   Writeln(SizeOf(TSet1));  Writeln(SizeOf(TSet2));  Writeln({$IFDEF FPC_OBJFPC}specialize {$ENDIF}Card(set1));  Writeln({$IFDEF FPC_OBJFPC}specialize {$ENDIF}Card(set2));  Readln;end. ---------------------------------------------------------------------- PascalDragon · 2018-02-18 10:36 Quote from: ASerge on February 17, 2018, 09:28:58 pmQuote from: PascalDragon on February 17, 2018, 07:32:02 pmSadly the "Unreachable code" warning can not be suppressed  1. This is compiled only in the development version. So? A potential Card() function or intrinsic would also only be part of trunk. Quote from: ASerge on February 17, 2018, 09:28:58 pm2. With {$mode objfpc} only two set sizes: 4 and 32. Thus, either one or the other part of "if" is an unreachable code. Yes, I know, but I'm more complaining about that even with {$warn off 6018} around the generic function the compiler doesn't remember that setting when specializing the function which is quite irritating, cause I thought that this should work. Probably a bug I'll have to look at... *sigh* Quote from: ASerge on February 17, 2018, 09:28:58 pm3. With {$mode delphi} this code, with little modification, is wrong, because, as @Thaddy say "The unused bytes are undefined, i.e. also dirty". Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program Project1;{$APPTYPE CONSOLE} {-$mode delphi}{$mode objfpc} {$IFDEF FPC_OBJFPC}generic{$ENDIF}function Card(const aSetValue: T): Integer;var  i: Integer;begin  Result := 0;  for i := 0 to SizeOf(T) - 1 do    Inc(Result, PopCnt(PByte(@aSetValue)[i]));end; type  TSet1 = set of 0..3;  TSet2 = set of 0..40; var  set1: TSet1 = [2];  set2: TSet2 = [4, 9, 23, 40];begin  set1 := [2];  set2 := [4, 9, 23, 40];   Writeln(SizeOf(TSet1));  Writeln(SizeOf(TSet2));  Writeln({$IFDEF FPC_OBJFPC}specialize {$ENDIF}Card(set1));  Writeln({$IFDEF FPC_OBJFPC}specialize {$ENDIF}Card(set2));  Readln;end.Yes, that definitely simplifies that affair without any unreachable code warning. I was much too focused on the difference between small and large sets plus I wasn't aware that PopCnt had an overload for Byte as I had no need to use that function before.  Small additional improvement: Unlike Delphi FPC can also make use of "inline" here as long as the method implementation is encountered before it is first called (e.g. in the case above that works).