9.5 Sets at the Low Level

There are some low-level sets built in that allow bit-by-bit manipulation of memory storage. Of course, to make any use of these, something must be known about the bit and byte level numeric representations on the underlying machine.

9.5.1 Bitsets

A number represented in binary form could be thought of as a set of bit positions enumerated from least significant (lowest number) to most significant (highest number) as shown in figure 9.3 for an eight bit number..

In this case, if the number is thought of as a SET OF [0 .. 7] the elements 6, 5, 3, and 2 are in the set, and the elements 7, 4, 1, and 0 are not in the set. This idea of interpreting storage as a set of bit positions is encapsulated in the following ADT:

The Modula-2 type BITSET has the implicit definition
TYPE
BITSET = SET OF [0 .. BitsPerBitset-1]
where the constant BitsPerBitset is implementation defined and the setting of each bit position determines whether the number of that position is an element of the set.

The constant BitsPerBitset is usually the same as the number of bits in a WORD, (or some integral multiple of this); often it is 16 or 32 and can be computed as:

BitsPerBitset := SIZE (BITSET) * SYSTEM.BITSPERLOC

If, for instance, as is often the case, the types CARDINAL, WORD, and BITSET all have the same size (in LOCs) the following program provides an alternative to the pair of mutually recursive procedures in Chapter 4 to print out a CARDINAL in binary form. It forcibly reinterprets a CARDINAL as a BITSET, and then prints out a one or a zero depending on whether that bit is represented in the set or not.

MODULE BitsetDemo;

(* Written by R.J. Sutcliffe *)
(* to demonstrate the use of bitset *)
(* using ISO standard Modula-2  *)
(* last revision 1994 03 01 *)

FROM STextIO IMPORT
  WriteString, WriteLn, SkipLine, ReadChar, WriteChar;
FROM SWholeIO IMPORT
  WriteCard, ReadCard;
FROM SYSTEM IMPORT
  CAST, WORD, BITSPERLOC;
  
CONST
  maxBitNum = BITSPERLOC * SIZE(BITSET) - 1;
  
PROCEDURE WriteWordBin (b: WORD);
(* Pre : WORD and BITSET have the same size *)

VAR
  bs : BITSET;
  count : CARDINAL;
  
BEGIN
  bs := CAST (BITSET, b);
  FOR count := maxBitNum TO 0 BY -1 
    DO 
      IF count IN bs
        THEN
          WriteCard (1,1)
        ELSE
          WriteCard (0,1)
        END;
      IF count MOD 8 = 0 (* break into groups of 8 bits *)
        THEN
          WriteChar (" ");
        END;
    END;
END WriteWordBin;

VAR
  theNumber : CARDINAL;
  answer : CHAR;
  again : BOOLEAN;
 
BEGIN
  WriteString ("This program tests a procedure to print ");
  WriteString ("cardinals in binary form");
  WriteLn;

REPEAT
  WriteString ("Enter the number to be changed ");
  WriteString ("to binary form ==> ");
  ReadCard (theNumber);
  SkipLine;
  WriteLn;
  WriteString ("The cardinal ");
  WriteCard (theNumber, 0);
  WriteString (" converted to binary form is: ");
  WriteLn;
  
  WriteWordBin (theNumber); (* one must know sizes are equal *)

  WriteLn;
  WriteString ("Do another? Y/N ");
  ReadChar (answer);
  SkipLine;
  again := (CAP (answer) = "Y");
UNTIL NOT again;

END BitsetDemo.

Observe that even the bit positioning from left (most significant) to right (least significant) is implementation defined; it is not necessarily the case in in the underlying hardware of all implementations. However, the numbering from zero (least signficant) to bitsperbitset (most significant) is defined to be the way that ISO standard Modula-2 must produce the results to the program, regardless of the hardware. Here is a sample run from this program module:

This program tests a procedure to print cardinals in binary form
Enter the number to be changed to binary form ==> 1

The cardinal  1 converted to binary form is: 
00000000 00000000 00000000 00000001 
Do another? Y/N y
Enter the number to be changed to binary form ==> 10

The cardinal  10 converted to binary form is: 
00000000 00000000 00000000 00001010 
Do another? Y/N y
Enter the number to be changed to binary form ==> 65535

The cardinal  65535 converted to binary form is: 
00000000 00000000 11111111 11111111 
Do another? Y/N y
Enter the number to be changed to binary form ==> 1000000
The cardinal  1000000 converted to binary form is: 
00000000 00001111 01000010 01000000 
Do another? Y/N n

9.5.2 Packed Sets

For the purpose of working with other types at the bit level, and provided their representation details are known, it is also possible to declare a more general kind of bitset, this time with a user-specified number of bits, rather than the fixed number allowed by the type BITSET. In addition, the underlying type in the more general case need not be CARDINAL [0..n], but may be any ordinal type (though CARDINAL is the most useful). Of course, if these sets are to be mapped to storage locations, the number of bit positions is most useful as a multiple of BITSPERLOC. Thus, in the above example, one could have declared:

TYPE
  CardSet = PACKEDSET OF [0..SIZE (CARDINAL) * BITSPERLOC - 1]

and then cast to an item of this type. Indeed, the type BITSET is just a specific built in subtype of the more general type PACKEDSET. By this means, the binary representation of other types, such as REALs could also be investigated.

In addition, there are some operations in SYSTEM that are intended to manipulate PACKEDSETS (including BITSETS) on the bit level. These include:

PROCEDURE SHIFT (val: <packset type>; numToShift : INTEGER) : 
            <same packset type>

which shifts all the bits of the pattern numToShift positions to the left or the right, (The bit on one end is shifted to oblivion and a zero comes in at the other end.) and

PROCEDURE ROTATE (val: <packset type>; numToRotate : INTEGER): 
            <same packset type>

which rotates all the bits of the pattern numToRotate positions to the left or the right, (The bit on one end is shifted to the other end.) The effects of the two are shown in figure 9.4

This capability is most useful when programming at the low system level where individual bits must be manipulated. Note that the effect of a right shift is to divide by 2. This is illustrated in the example below. Note that it does not make use of any special knowledge about the sizes of the types in bits, but computes them from information available from SYSTEM.

MODULE ShiftDemo;

(* Written by R.J. Sutcliffe *)
(* to demonstrate the use of packedset *)
(* using ISO standard Modula-2  *)
(* last revision 1994 03 02 *)

FROM STextIO IMPORT
  WriteString, WriteLn, SkipLine, ReadChar, WriteChar;
FROM SWholeIO IMPORT
  WriteCard, ReadCard;
FROM SYSTEM IMPORT
  BITSPERLOC, SHIFT, ROTATE, CAST;
  
CONST
  maxBitNum = BITSPERLOC * SIZE(CARDINAL) - 1;
  
TYPE
  CardSet = PACKEDSET OF [0..maxBitNum];
  
PROCEDURE WriteCardBin (num: CARDINAL);

VAR
  count : CARDINAL;
  numSet : CardSet;
  
BEGIN
  numSet := CAST (CardSet, num);
  FOR count := maxBitNum TO 0 BY -1 
    DO 
      IF count IN numSet
        THEN
          WriteCard (1,1)
        ELSE
          WriteCard (0,1)
        END;
      IF count MOD 8 = 0 (* break into groups of 8 bits *)
        THEN
          WriteChar (" ");
        END;
    END;
END WriteCardBin;

VAR
  num : CARDINAL;
  answer : CHAR;
  again : BOOLEAN;
 
BEGIN
  WriteString ("This program illustrates bit shifting ");
  WriteLn;

REPEAT
  WriteString ("Enter the number to be shifted ");
  ReadCard (num);
  SkipLine;
  WriteLn;
  WriteString ("The cardinal ");
  WriteCard (num, 0);
  WriteString (" binary: ");
  WriteCardBin (num);
  WriteLn;
  WriteString ("Shifted one position right yields ");
  num := CAST (CARDINAL, SHIFT (CAST (CardSet, num), -1));
  WriteCard (num, 0);
  WriteString (" binary: ");
  WriteCardBin (num);
  WriteLn;
  WriteString ("and, then rotated one position right yields ");
  num := CAST (CARDINAL, ROTATE (CAST (CardSet, num), -1));
  WriteCard (num, 0);
  WriteString (" binary: ");
  WriteCardBin (num);
  WriteLn;
 
  WriteString ("Do another? Y/N ");
  ReadChar (answer);
  SkipLine;
  again := (CAP (answer) = "Y");
UNTIL NOT again;

END ShiftDemo.

Here is a brief run to illustrate:

This program illustrates bit shifting 
Enter the number to be shifted 1

The cardinal  1 binary: 00000000 00000000 00000000 00000001 
Shifted one position right yields  0 binary: 00000000 00000000 00000000 00000000 
and, then rotated one position right yields  0 binary: 00000000 00000000 00000000 00000000 
Do another? Y/N y
Enter the number to be shifted 5

The cardinal  5 binary: 00000000 00000000 00000000 00000101 
Shifted one position right yields  2 binary: 00000000 00000000 00000000 00000010 
and, then rotated one position right yields  1 binary: 00000000 00000000 00000000 00000001 
Do another? Y/N y
Enter the number to be shifted 65535

The cardinal  65535 binary: 00000000 00000000 11111111 11111111 
Shifted one position right yields  32767 binary: 00000000 00000000 01111111 11111111 
and, then rotated one position right yields  2147500031 binary: 10000000 00000000 00111111 11111111 
Do another? Y/N n

Observe the difference between the rotate and the shift when there is a one in the rightmost position.


Contents