Questions

What are abstract data types?

An abstract data type is a collection of values and operations on those values, considered without reference to how such values might be represented or how such operations might be implemented. The implementation is what is being taken away or eliminated by abstraction.

When dealing with abstract data types, do we or do we not need to consider implementation? And what isn't an abstract data type? It seems like they all fit this mold...

The term `abstract' is not used to classify data types -- it's not that some of them are abstract and others are concrete. An abstract data type is one that is being considered in a particular way -- without reference to its representation, without considering how it would be implemented. In the handout on characters, it looks at first glance as if I'm going over the same ground twice -- what's the difference between the operation upcase (as specified in the first half of the handout) and the Pascal function Upcase (as defined in the second half)? The answer is that in the first half of the handout, I'm considering characters as an abstract data type -- just the values and the possible operations on them -- and in the second half I'm showing how to implement this type fully in Pascal (at which point it's no longer an abstract data type, because I'm no longer considering it without reference to its representation).

Considering the data type first as an abstraction is actually a helpful programming technique, because it enables you to think about and design a selection of operations that reflects the nature of the type itself. Unless instructed to adopt this approach, most programmers ``design'' whatever they think will be easiest to implement, which is a good short-term strategy for building small programs but fails badly in the longer run -- irrelevant and accidental characteristics of a particular programming language, operating system, or networking environment show through too much.

I understand how the computer tells which integer, character, or real number it is reading. What I don't understand is how the computer knows if it is reading a character, an integer, or a real. Is there some part of a word that identifies what data type it is?

In some programming languages, such as Scheme, it is necessary for each value that is stored in memory to be tagged in the way you suggested with an indication of the type to which it belongs. Such tags are not needed in Pascal, since all the necessary type information is already available when the program is compiled.

When translating a call to the Read or ReadLn procedure, the Pascal compiler examines each variable for which a value must be read in and infers the type of that variable from its declaration. (Since every Pascal variable must be declared before it is used, the compiler will already have seen the declaration and stored the variable's type in a ``symbol table'' that it builds and maintains during compilation.) The compiler then generates different machine instructions, depending on whether the program calls for the reading of a Char, Integer, or Real value.

In other words, although it appears to the Pascal programmer that there is only one Read procedure, there are actually three different ones, and the compiler decides which one to use for each value that is read by inspecting the type of the variable in which the value will be placed.

Where does the computer get the ability to distinguish different data types?

The computer has no such ability. If the programmer stores a value of type Real in a particular location in memory, and then lies to the computer by telling it that there is a value of type Integer stored there, the computer will happily interpret the pattern of zeroes and ones that occupies that storage location as if it were an integer.

Pascal makes it rather difficult for the programmer to tell such a lie (and specifies that it is an error for him to do so). In some programming languages, it is completely impossible to express the lie; in others, it is extremely easy and indeed routine -- the programmer is simply made responsible for the consequences of lying to the computer.

Why is Pascal so strictly typed? Having learned Scheme two years ago, I had grown accustomed to being able to put nearly whatever I wanted into a variable, etc. I miss that flexibility, and though I recognize that most languages cannot be so dynamic, Pascal seems to taking typing to an extreme.

There are three main reasons:

Each memory location has an address, but how does the computer know which address to assign, say, each time you declare an array?

For global variables, the compiler simply starts at 0 and allocates successive locations for successive variables. Often, as on the HPs, a few locations may be skipped in order to allow advantageous alignments; for instance, if the address of the next available location is 390, a variable of type Integer might be given the address 392, leaving bytes 390 and 391 unused, because an integer can be transferred more rapidly from the memory to a register in the central processing unit if its address is divisible by 4.

When the compiled program is executed, the global ``addresses'' generated by the compiler are actually interpreted as offsets from a base address established by the operating system.

The addresses of parameters and local variables are handled similarly, except that their ``base addresses'' are established during program execution by the Pascal run-time system rather than by the operating system.

Since the character digit-zero is represented by the bit-pattern 00011000 in ASCII, and the integer 48 is also 00011000, and the forty-ninth component of an enumerated type is also 00011000, how can the computer distiguish one from another?

Actually, an integer value requires thirty-two bits under HP Pascal, so the representation of the integer 48 is actually 00000000000000000000000000011000. But in general your question is a good one: If values of different data types have the same bit-pattern and occupy the same amount of storage, how can the computer tell them apart?

The answer is that it cannot. If the programmer stores the forty-ninth value of an enumerated type in a particular location in memory, and then lies to the computer by telling it that there is a value of type Char stored there, the computer will happily interpret the pattern of zeroes and ones that occupies that storage location as digit-zero.

Pascal makes it rather difficult for the programmer to tell such a lie (and specifies that it is an error for him to do so). In some programming languages, it is completely impossible to express the lie; in others, it is extremely easy and indeed routine -- the programmer is simply made responsible for the consequences of lying to the computer.

Pascal, you have said in class, makes it difficult for the programmer to examine data as types other than Pascal thinks they are. An integer cannot be read as a real, or a char. Why is this worth mentioning? Why would somebody want to look at data as if it were something it's not?

Because sometimes it's more efficient to bypass the interface that the designer of the data type set up. For instance, suppose that you want to know whether the value of the Integer variable Position is evenly divisible by 4. The designer of Pascal's Integer data type wants you to write

Position mod 4 = 0
to find this out. This involves doing a division, which is the most time-consuming of the arithmetic operations. If you happen to know that on HPs an integer is divisible by 4 if, and only if, bits 1 and 0 of its internal representation are ``off'' -- zeroes -- and if your programming language allows you to test these bits using machine instructions that are faster than the division operation, you will be able to speed up your program by looking at the integer as a sequence of bits instead.

Another example: Suppose that you have determined that the value of the variable Ch, of type Char, is a lower-case letter, and you want to replace it with the corresponding capital letter. In Pascal, you write something like

Distance := Ord ('a') - Ord ('A');
Ch := Chr (Ord (Ch) - Distance)
for this purpose. The C programming language encourages programmers to perform arithmetic directly on characters, as if they were integers. If you could do this in Pascal, you would write

Distance := 'a' - 'A';
Ch := Ch - Distance
Which is better? A good compiler would generate the same machine instructions in either case. The Pascal version is perhaps clearer but more cumbersome.

I was talking with my friend Greg from Madison, and the issue of Pascal's strong typing came up. I remembered you talking about how hard it is to get a sorting procedure to get more than one type of input, and Greg had an idea of how to solve it. He thought of a function that could be placed which would help with the process. Before the sorting procedure is called, a function stores the data, be it chars or reals, into a binary file of that type. The function would as need to take in a symbol of what type the binary file contained. Then the function would have a case statement that would call the sorting procedure with the correct type, adding a code to tell it how to unravel it. I would maybe try to write this, and want your opinion on whether it is possible, or even worth the time it would take as a constant before sorting.

Let's see. You could write data of any type into a binary file, then have the operating system attach the same file to a different Pascal variable that would treat the data in the file as being of some neutral type, perhaps an array of bytes, distinguished only by its length. You could read the file into an array of objects of the neutral type, sort it, and put it back in the file, then have the operating system reattach the file to the original Pascal file and read the sorted data back in from it.

This could work. In the middle of the process, you're lying to Pascal about the nature of the data being sorted, but Pascal will never know the difference. A similar approach along the same lines, which would be more efficient because it would avoid the need for file operations, would be to define a variant record type with two variants, one the original data type, the other the byte array of the appropriate length. Store the data in the first place using the first variant; call a sorting procedure in which the data is treated as being of the type of the second variant; output the data using the first variant again.

In HP Pascal, still another possibility is available whenever the data values are accessed through pointers. (As you may have inferred from the modules we've recently studied, this is often the case in real Pascal programs.) By declaring the parameter of a sorting method to be an array of values of HP Pascal's LocalAnyPtr type, one can use the sort with any array of pointers, regardless of their real base type.

Should I worry about remembering the difference between the types of parentheses and brackets in BNF grammar if I am comfortable with syntax diagrams?

If you can find an accurate syntax diagram for every syntactic construction that you're interested in, and if you think that such diagrams are more readable than BNF productions, then you don't need to be too concerned about the mechanics of BNF. Besides, the particular conventions that authors use to write BNF vary, so that every time you run across a BNF grammar you may have to study the context in order to figure out exactly what the parentheses, brackets, and braces mean.

However, the conventions that Cooper and Walker use aren't all that hard to learn: Parentheses are solely for grouping alternatives. Brackets enclose optional constituents. Braces enclose constituents that can be repeated 0 or more times.

To put it in terms of syntax diagrams: Parentheses disappear in syntax diagrams. Brackets correspond to temporary forks in the track, one line containing the optional construction, the other one empty; the two lines merge again afterwards. Braces correspond to loops in the track; one can go past the loop on the main track, or around the loop any number of times before proceeding.

How important is it to understand both syntax diagrams and BNF grammars? Is BNF more widely used?

BNF is far more widely used than syntax diagrams, mainly because it's easier to store BNF grammars in text files and to write programs that will read, parse, and do useful things with them.

Why does Pascal require you to compile a program before you run it? I learned to program in BASIC, and it translated as it ran. Is it that much more efficient to compile it beforehand? Or is it done to catch errors before the program is run?

Efficiency is the main reason. There used to be several Pascal processors that used interpretation rather than compilation; they would translate from Pascal to an intermediate ``P-code,'' which the user would then execute on a ``Pascal virtual machine.'' The Pascal virtual machine was a piece of software that read and interpreted P-code. The whole process was similar to the process of saving and later running a BASIC program on, say, an Apple II; BASIC programs on the Apple II were stored in a reduced form that was interpreted by the RUN command.

The translation part of the Pascal interpreter ran faster than a full compilation; the difficulty was that the Pascal virtual machine was usually very slow, perhaps five to ten times slower than compiled code. The interpreter was therefore used primarily during program development. The finished version would be run through the compiler before being released.

Pascal interpreters have now almost disappeared, because Pascal compilers are so much faster on modern computers. When translation of a thousand-line program took ninety seconds under pi (a P-code generator) and five minutes under pc, it was worth while for a student pressed for time to use pi. But when pi takes a second and a half and pc takes five seconds, what's the point?

Is it better in general to write a long program that uses lots of very simple procedures from a library or to do the same job with a short program containing very efficient but very specialized procedures?

Usually it is better to write the first version of the program with general procedures that are already available. If this first version is too inefficient or clumsy, you can rewrite it later to use more specific procedures, but in most cases rewriting is superfluous -- the first version works well enough.

When describing a procedure should I mention procedures it calls?

Only if it simpifies or clarifies the description of the current procedure. It's a question of style, not correctness; but in general the description will better reflect the modularity of the procedure if you avoid gratuitous references to other procedures.

If procedure A calls procedure B, and the definition of procedure B is completely nested within the definition of procedure A, then there's a somewhat sounder argument for mentioning procedure B in the opening comment for A; but even in this case it is more usual for the relevant facts to be placed in the comment at the beginning of the definition of procedure B instead.

Suppose in my program I have two layers of procedures. The first layer is Procedure1 and Procedure2. The second layer is Procedure11 and Procedure12, both of which belong to Procedure1 and have nothing to do with Procedure2. There is a data type that I want to use in Procedure1 and as the type of a parameter of Procedure11. This data type has nothing to do with Procedure2 or Procedure12. Do you think I can just declare this data type in Procedure1?

That's the ideal place for it. In general, if you have any identifier that is used only inside a procedure, it should be defined or declared inside that procedure.

Variable names give me trouble, especially as these programs grow longer and more complex. Is there a set of guidelines that we might want to follow? I miss the ability to give variables names like player_r for player record -- somehow playerr doesn't do it, and PlayerRecord is too many characters.

HP Pascal allows you to use the underscore as a break character, as you prefer. And it's fairly easy to write a utility that will strip all the underscores out of a program when you're ready to port it to some less tolerant implementation of Pascal.

In general, global identifiers should be very explicit, even at the expense of more typing; an abbreviation is more confusing if the point at which it is used is far away from the point at which it is defined, as often happens with globals. If you're going to use a short, cryptic identifier, be sure to attach an explanatory comment at the point where it is defined.

How important are variable names? I have noticed that some programmers use very elaborate variable names, making the program easier to decipher yet a lot messier and harder to read, while other programs I have seen in books are written with very basic variable names, often just letters. Do you feel it is far better to use elaborate variable names? If so, do you tend to take points off for programs that don't use elaborate names?

Choosing meaningful identifiers is a fundamental part of good programming style. Often I have found that an author's poor programming style undermines an otherwise excellent textbook -- and the most common error in such cases is the use of cryptic identifiers, often arbitrarily selected single letters. A case in point is Robert Sedgewick's Algorithms, which we've tried to use twice in different courses in the department, because the prose explanations are so good. Both times, the students found the accompanying source code unintelligible -- useless at worst, unhelpful at best..

The original cause of the problem is that the first programming language of many programmers was either FORTRAN or some rudimentary form of BASIC. In FORTRAN, it's a rule of the language that no identifier can be more than six characters long. Also, many programs directly reflect the mathematical notation in which the problems they solve are specified; since mathematicians use single letters as variables, FORTRAN programmers tend to do so as well. In some dialects of BASIC, including the one I first learned, the interpreter was capable of recognizing an identifier only if it was either a single letter or a single letter followed by a digit. Such restrictive programming languages produced a generation of programmers with the bad habit of using single-letter identifiers for everything.

The rule that I go by is that every identifier should be self-explanatory unless it is used only within a few lines of its point of definition. This means that I tend to use long identifiers in global definitions and declarations but sometimes use short ones locally. I also avoid abbreviations and acronyms; too often they mystify the reader.

However, I generally take points off for this error of style only if it makes it more difficult for me to understand a student's paper. Usually this happens only in combination with other stylistic errors (insufficient documentation, over-long procedures, irregular indentation, etc.).

After every occurrence of end, should I indicate in braces what is ending, thus:

end {for loop}

It's up to you. A lot of programmers seem to find comments of this kind helpful, especially when control structures are deeply nested. I don't object to them, but I don't find them particularly helpful either, because I'm very careful and regular about indentation and can almost always match up the beginning and end of a compound statement by observing which lines are indented to the same column.

Do you have any general guidelines for comments in source code we turn in? (My high school CS teacher had a very standard format that she demanded we follow, so I was wondering if you had a similar policy.) My main concern is that I might be overdocumenting my source code.

It is practically impossible to overdocument source code, though it is possible to document it too mechanically, without giving the comments enough thought. (The usual symptom of this is comments that are redundant, repeating information that is immediately obvious from the code itself rather than explaining the purpose of a variable or a procedure or the rationale for a programming choice.) Quality is more important than volume.

I have two habits that I recomment to students: (1) I write a long opening comment at the beginning of each program, in which I describe the problem that the program is supposed to solve and the general approach to a solution that is used in the program. (2) I also write a one- or two-line comment on every definition and declaration in the program, explaining what the identifier that is being defined is for. As a result, I seldom have to include comments in the executable part of a program, procedure, or function.

For instance, I've finished my solution to exercise 1. It begins with a comment of forty lines, and after this opening comment about 42% of the subsequent lines are inside comments.

When does it become appropriate to use HP Pascal's Assert function to enforce preconditions?

Please use it as soon as you have learned its syntax and the meanings of its parameters, which are discussed in chapter 9 of the HP Pascal / HP-UX reference manual.

My question is about the Assert function and the exception codes declared in the sequence module. (Or any of the modules in your handouts, for that matter.) You declare an ExceptionException constant, but the only place this enters into the code is to check to see if the exception code generated is valid. If you only call the Assert function with valid codes, why is it necessary to check the incoming exception code? Is it just a design principle? Would you ever expect to crash out of the program with this exception code? What could cause the program to crash with this error?

In principle, it's not necessary to check the incoming exception code. Since the SequenceExceptionHandler procedure is not exported, one can see all the places at which it can possibly be invoked just by reading through the implement section of the module in which it is defined. In each of these places, it is invoked with a valid exception code. This means that the program cannot possibly generate the ExceptionException error and that, in principle, it is pointless to provide for such an error.

The problem with this principle is that it is too fragile; it wouldn't take much of a change in the module to break it, and it depends on the ``voluntary cooperation'' of a lot of separate procedures and functions that may in the future be rewritten, by me, by you, or by someone who borrows the code from the WWW site. Providing the ExceptionException is a kind of defensive programming -- overdesigning the software to compensate for the fact that very frequently during the execution of real-world programs things that ``can't happen'' happen.

I'm having trouble writing Assert calls in my own programs. From a defensive-programming standpoint, would you prefer to see an Assert call in the following procedure block, or not?

procedure InsertIntoTree(var Tree    : TreePtr;
                             NewInfo : DataType);

procedure InitEntry(var Ptr  : PtrType;
                        Data : DataType);

begin
  Ptr^.Info:=Data;
  Ptr^.Left:=NIL;
  Ptr^.Right:=NIL;
end;

begin
  if Empty(Tree) then begin 
     new(Tree);
     InitEntry(Tree);
  end 
  else
    if LessThan(Tree^.Info,Data) then
       InsertIntoTree(Tree^.Left)
    else InsertIntoTree(Tree^.Right);
end;
Since InitEntry is a local procedure to InsertIntoTree, it can only be called from InsertIntoTree, which properly checks the implicit precondition that Ptr is not NIL. Is there a need for an Assert statement? On one hand, dereferencing a pointer should always be checked by an Assert procedure, but if InitEntry is only called when its preconditions have already been checked and cannot be called from another procedure, is there really a purpose in providing an Assert call, other than perhaps somebody who doesn't understand the library will come and revise it, and not properly check the precondition before invoking InitEntry? Would you recommend checking preconditions anyway, just in case I were to go and add to the library later on and forget to be careful?

No, in this case I don't see the need for a call to Assert. I might feel slightly differently if the call to InitEntry were located farther way from the definition of that procedure, but in this case it's easy to detect the relationship between the two procedures.

However, your question illustrates a curious phenomenon in programming: When you're not sure whether an assertion is needed, or (in other cases) when you try to write an assertion and find that it's very cumbersome, it's often a sign that you're trying to do the wrong thing -- usually, that you're trying to modularize the program incorrectly. In the particular case you cite, you can avoid the whole problem by moving the call to New into the InitEntry procedure where it belongs:

procedure InsertIntoTree (var Tree: TreePtr; NewInfo: DataType);

  procedure InitEntry (var Ptr: PtrType; Data: DataType);
  begin
    New (Ptr);
    Ptr^.Info := Data;
    Ptr^.Left := nil;
    Ptr^.Right := nil
  end;

begin
  if Empty (Tree) then
    InitEntry (Tree, NewInfo)
  else if LessThan (Tree^.Info, NewInfo) then
    InsertIntoTree (Tree^.Left, NewInfo)
  else
    InsertIntoTree (Tree^.Right, NewInfo)
end;
In the code for Walker's procedure to print the information for a baseball player, he declares Ind as a variable parameter. Since the procedure is printing (and thus not changing) information, what is the rationale for declaring Ind as a variable parameter?

It makes the mechanism for the procedure call more efficient. When a large data structure is passed by value, it must be copied into the storage allocated for the parameter; this copying process takes an amount of type proportional to the size of the structure. When the same data structure is passed by reference, only its address is actually copied into the storage set aside for the procedure. Since addresses are small and have a fixed size, this mechanism is faster.

I am not too clear on the distinction between an error and a violation. Page 1 of Standard Pascal makes them sound like one and the same, and page 100 draws a distinction between them.

There are two kinds of violations of the rules of standard Pascal: those that can be detected by inspecting the text of the alleged program, without trying to execute any of it, and those that can only be detected by executing part or all of the alleged program. Violations of the latter kind are errors; violations of the first kind don't have a separate name, but when, on page 100, Cooper contrasts errors with ``violations,'' he means violations of the first kind.

A standard Pascal processor is required to detect and report violations of the first kind. It is not required to detect all errors if its documentation lists the classes of errors that that it does not detect.

Could you go over the difference between implementation-defined and implementation-dependent?

Sure. First, here's what the standard says:

3.3. Implementation-Defined. Possibly differing between processors, but defined for any particular processor.

3.4. Implementation-Dependent. Possibly differing between processors and not necessarily defined for any particular processor.

For example, HP Pascal provides both MaxInt and MinInt; these are pre-defined constants, the greatest and the least values of the Integer data type. MaxInt is implementation-defined: Every standard Pascal processor must recognize this identifier as a constant, but it may denote different values in different Pascal systems. MinInt is implementation-dependent: Some standard Pascal processors will not pre-define it, and those that do may give it different values.

Are we allowed to use the "non-ANSI-Pascal" features of pc for our programs? In other words, can we invoke pc without the -A option which comes defined with pgo in the standard MathLAN account?

Yes. For example, the Pascal standard says that it's the job of the operating system to attach files for input and output to the relevant Pascal file variables. Consequently, the Reset and Rewrite procedures in standard Pascal take only one argument. But the designers of HP Pascal instead left it up to the programmer to attach such files, and hence provided two-argument versions of these procedures:

Reset (Source, '/users/spelvin/frogs.dat');
Rewrite (Target, 'frogs.out');
In this case, I encourage you to use the two-argument forms even though they are non-standard. It is practically impossible to ensure the portability of calls to Reset and Rewrite anyway, since there is so much variety in the mechanisms that are used to attach files to Pascal file variables.

Is it possible, using the HP compiler, to nest comments?

No. Nesting of comments is contrary to the standard (see Cooper, p. 7). Some implementations of Pascal allow the nesting of comments, but this is a mistake.

The following test program can be used to determine whether a given implementation of Pascal allows nesting of comments:

program Comments (Output);
const
    { { }
    Nestable = False;
    First = '} Nestable = True; {';
    Second = '} Third = '''{' { };
begin
    WriteLn ('It is ', Nestable, ' that comments are nestable.')
end.
Under HP Pascal, this program produces the output

It is FALSE that comments are nestable.
I'm a little confused about program parameters. I know that Input and Output are necessary to read from the keyboard and write to the screen. It seems that any other parameters are somewhat superfluous, since you have to redefine them in the VAR section of the program block anyway.

In the original implementation of Pascal, the parameters in the program header were supposed to correspond to command-line arguments. This requirement was abandoned before the language was standardized, since Pascal was implemented under some operating systems that either had no notion of a command-line argument or did not provide easy access to them, but many implementations of Pascal continue to make some special use of the program parameters, so simply deleting them from the language would invalidate a lot of Pascal code.

Would it be possible to invoke a program within the program, like a procedure within the procedure, thus allowing us to make programs themselves recursive?

The only way to do this, within Pascal's syntax, is to move the body of your main program into a Control procedure, replacing it with a single-statement program body that is a call to this procedure, and then to invoke Control recursively when you want to ``invoke the program.'' There is no way to re-invoke the main program body from within a procedure or function definition in Pascal.

Is there a random number generator in HP Pascal? I couldn't find any mention of it in the LaserROM manual.

No, there isn't. Here's a canned one that I can recommend:

var
  RandomSeed: Integer;
   { the current value in the sequence of integers produced by the
     generator; this variable must be initialized so as to provide a
     starting point from which to develop values }

{ The Randomize procedure sets the value of RandomSeed to an initial
  value that depends on its parameter. }

procedure Randomize (Offering: Integer);
begin
  RandomSeed := 1 + Offering mod MaxInt
end;

{ The Random function uses the linear-congruential method to generate a
  pseudo-random value in the range (0.0, 1.0].  The particular generator
  mentioned here is proposed as a standard by Stephen K. Park and Keith
  W. Miller, in ``Random number generators: Good ones are hard to find,''
  COMMUNICATIONS OF THE ACM 31, 1192--1201.  To avoid integer overflow, the
  modulus is separated into two parts, Quotient and Remainder, such that
  Modulus = Quotient * Multiplier + Remainder, and similarly RandomSeed is
  separated into a high segment and a low segment such that RandomSeed =
  Quotient * HighSegment + LowSegment.  The new value to be computed is
  then 

    Multiplier * RandomSeed mod Modulus

      = (Multiplier * Quotient * HighSegment + Multiplier * LowSegment)
            mod (Multiplier * Quotient + Remainder)

      = (Multiplier * Quotient * HighSegment + Remainder * HighSegment
            + Multiplier * LowSegment - Remainder * HighSegment)
            mod (Multiplier * Quotient + Remainder)

      = (Multiplier * LowSegment - Remainder * HighSegment)
            mod (Multiplier * Quotient + Remainder)

  which can be computed without overflow, since the highest possible value
  of Multiplier * LowSegment - Remainder * HighSegment, even assuming a
  HighSegment value of 0, is less than Multiplier * Quotient, which is less
  than Modulus, which is equal to MaxInt, and the lowest possible value of
  the same expression, even assuming a LowSegment value of zero, is
  -Remainder * HighSegment, which is greater than -Multiplier * Quotient,
  etc.

  The article cited above recommends that 16807 be used as the value of
  Multiplier, and consequently 127773 as the value of Quotient and 2836 as
  the value of Remainder.  In a subsequent note (in ``Technical
  Correspondence,'' COMMUNICATIONS OF THE ACM 36, number 7, 108--110), Park
  et al. suggest the multiplier 48271 instead.  This change has been made
  in the code below. } 

function Random: Real;
const
  Multiplier = 48271;
  Modulus = 2147483647;        { = 2^31 - 1 }
  Quotient = 44488;            { = Modulus div Multiplier }
  Remainder = 3399;            { = Modulus mod Multiplier }
var
  HighSegment: Integer;
    { the number of times Quotient goes into RandomSeed }
  LowSegment: Integer;
    { the remainder when RandomSeed is divided by Quotient }
  Test: integer;
    { RandomSeed * Multiplier mod Modulus, possibly ``wrapped around'' to a
      negative number that is off by Modulus } 
begin
  HighSegment := RandomSeed div Quotient;
  LowSegment := RandomSeed mod Quotient;
  Test := Multiplier * LowSegment - Remainder * HighSegment;
  if 0 < Test then
    RandomSeed := Test
  else
    RandomSeed := Test + Modulus;
  Random := RandomSeed / Modulus
end;
Is there a relationship between the possible size of Random and the size of the seed number?

Standard Pascal doesn't provide a Random function, or indeed any kind of a random-number generator, so I'll take this as a question about the random-number generator that Walker develops on pages 106 through 109 of the text.

The range of possible values returned by a Random function is independent of the range of values of the seed, but the particular value returned on any one call to Random is proportional to the current value of the seed.

Could you explain big-O notation?

Take any two functions, f and g, that take positive integers as arguments and produce positive real numbers as values. The statement that f is of order g -- in symbols, f(n) = O(g(n)) -- means that, once the arguments are sufficiently large, the ratio between the values produced by f and those produced by g is bounded by a constant, so that in effect f grows no more rapidly that some fixed multiple of g.

Formally, f(n) = O(g(n)) is defined to mean that there is a positive integer m and a positive real c such that, for every integer n greater than or equal to m, f(n) <= cg(n).

In the context of the classification of algorithms, the function g that describes the order is some simple function like n^2 or lg n, and the function f is intended to characterize the running time of the algorithm as a function of the size of its input.

Is there a good way to evaluate average-case efficiencies of algorithms?

Sometimes. There is no universally applicable method for analyzing algorithms, any more than there is a single method for proving mathematical theorems. Often it is easier to analyze the worst-case efficiency of an algorithm than to analyze its average-case efficiency; it may even be difficult to decide how to take an average for, say, a sorting algorithm: Should one assume that every initial permutation of the elements of an array is equally likely, or should one try to weight the average in favor of cases that might arise more frequently in practice?

In the bignum handout, you say that the division algorithm is proved correct. Is there a formalized method of proving the correctness of algorithms? It seems like something which mathematical inductive reasoning could apply to quite well. And if there is such a way, is it just too complicated to apply to windowing systems?

Yes, a lot of work has been done on methods for constructing formal proofs of correctness. Two good introductory books are A method of programming, by Edsger W. Dijkstra and W. H. J. Feijen (Reading, Massachusetts: Addison-Wesley Publishing Company, 1988), and The science of programming, by David Gries (New York: Springer-Verlag, 1981).

There are five reasons why correctness proofs are not routinely constructed for windowing systems:

  1. The proof techniques that have been developed don't scale up from small programs to large ones very well, especially in programs that don't have a modular structure; the propositions that have to be proven increase too rapidly in size and complexity to be intellectually tractable.
  2. The proof is relative to a complete formal specification of what the desired behavior of the program is; it is trivial to write such a specification for a division algorithm, but arriving at a complete formal specification for a windowing system is itself a difficult and error-prone process.
  3. Most real-world programmers either don't know the proof techniques or haven't used them enough to become comfortable with them, so they tend to regard correctness proofs as very time-consuming and not cost-effective.
  4. Programming languages in general don't provide any support for the construction of correctness proofs. Programmers would be much readier to provide correctness proofs if the compiler required them and checked them automatically.
  5. Management in companies that develop software has trouble seeing any connection between time spent in proving correctness and the quality of the code produced.
Is there any way to limit the number of errors pc will return?

No, but you can redirect the error messages to a file, and then use an editor or a pager to inspect the file. To tell pc to store the error messages in a file called errors, overwriting the previous contents of that file (if any), add the redirection clause >&! errors to the end of the command, thus:

pc -o myprogram myprogram.p >&! errors
Be sure to leave a space after the exclamation point.

What is a good way to track down run time errors on the HP's? My program compiles but won't run.

There's an interactive debugger named xdb. Here's how it works:

You use xdb when you have a program that is syntactically correct (the compiler can succeed in translating it) but semantically incorrect (it gives the wrong answers, at least part of the time). To prepare your program for use with xdb, you must compile it with the -g option:

pc -o frogs -g +N frogs.p
The -g option directs the compiler to provide the ``hooks'' that xdb requires.

Subsequently, you can start up xdb to debug an executable called frogs by typing

xdb frogs
in an hpterm window.

When you activate xdb, it takes over your hpterm window and divides it into two subwindows, separated by a status line. The subwindow below the status line records your interactions with xdb; the one above the line displays part of the source code for the program that you're working on. The status line itself tells what file contains the part of the source code that is being displayed, what line of that file contains the statement that will be executed next, and what function that statement belongs to.

The text in the display subwindow cannot be edited; it passively exhibits the source code from which the executable was compiled. If you want to debug interactively, making changes in your code, recompiling, and reloading the debugger, you'll need at least two windows: Emacs to do the editing and recompiling, and xdb-in-hpterm to do the debugging. (To compile from within Emacs, click on the meshing-gears icon, third from the right on the toolbar, and type in the command that performs the compilation.)

You issue commands to xdb at the prompt, a greater-than sign, in the interaction subwindow. Output from the program is done by default by default in the same subwindow, which is sometimes confusing. To arrange for the program's output to be done in a different window, proceed as follows: Start up a different hpterm window and type tty at the hpterm prompt; you'll see a file name, something like /dev/tty/ttyp8. This is the operating system's way of identifying the window as a source of input and a receiver for output. Start xdb with the -o and -e options, specifying this file name after each one:

xdb -o /dev/tty/ttyp8 -e /dev/tty/ttyp8 frogs
This will direct xdb to connect frogs's Output and standard error facilities to the specified window. (You could even connect them to different windows if you prefer.)

There is also an -i option that can be used to connect stdin to a different window, but it's less satisfactory because it doesn't arrange for the input to be echoed -- you won't be able to see what you're typing.

Starting the program. The r command starts executing the program at the beginning. Execution continues until the program crashes or a breakpoint (see below) or the end of the program is reached. Unless you're trying to determine the point at which your program is crashing, you generally want to set some breakpoints before you type r. You can pass command-line arguments to your program by typing them after the r; subsequent uses of r provide the same command-line arguments automatically, unless you override them with new ones.

Executing the program one statement at a time. The s command executes exactly one statement in the program, the one on the line that is marked (with a greater-than sign) in the display subwindow. The S command does the same thing, except that it treats a function call as a single step, while s breaks the function down into its component statements. If you supply an integer after either s or S, the specified number of statements is executed as a group.

Viewing a different section of the source code. The v command adjusts the display so that a specified line of code is visible. An unsigned integer after v requests the line at that position in the currently displayed file. You can ask for a different file by typing the file name after the v, and for a particular line of that other file by attaching a colon and the line number after the file name. To move the display up or down by a specified number of lines, use the + command (down) or the - command (up). In either case, write the number of lines after the sign. The V command returns the display to the line that will be executed next.

Setting a breakpoint. To stop program execution at a specific point in the program, use the b command: b followed by a line number (or a file name, a colon, and a line number) marks that line as a breakpoint, so that execution stops every time that line is reached. The command bb sets a breakpoint at the beginning of the currently executing function. The command bp sets a breakpoint at the beginning of every function. The command lb displays a list of the breakpoints that have been set. The command db removes all those breakpoints; if followed by an integer, it removes only the breakpoint with that serial number (as shown by lb).

Continuing from a breakpoint. To resume program execution after a breakpoint, use the c command. (Alternatively, you could start over again with r, or advance cautiously a step at a time with s.)

Displaying the value of a variable. The p command prints out the value of a variable; type the variable after the p. The variable can be a simple identifier, an array reference, a structure or a field of a structure, or a dereferenced pointer expression. After you have displayed the value of an array element, the command p+ displays the next element of the array, and p- displays the previous element.

Changing the value of a variable. To modify the value of a variable, use the p command, but type an equal sign and the new value after the variable.

Exhibiting the run-time stack. The t command shows the names and parameter values of functions that have been invoked but have not yet exited. The T command shows the same information, along with the values of all local variables in each of those functions.

Exiting from xdb. The q command shuts down xdb in an orderly way.

Could you please explain what exactly a bus error is?

A bus is a communication pathway connecting two or more devices, such as the central processor and the memory of a computer. A bus error occurs when a program tries to use the bus without meeting the preconditions for its correct use. For example, the central processor recovers a value from a storage location in memory by sending its address to the memory unit by means of a bus. If it generates a bogus address -- one that does not denote any location in the actual memory -- it's possible for a bus error to occur. One common cause of such an error is dereferencing a pointer variable to which no value has ever been assigned; the random bit pattern in the pointer variable is then treated as an address, and such an address is very likely to be bogus.

Why is it that so often changing the order of seemingly unrelated statements will completely change the workings of a program?

Usually, the explanation is that each of the statements has side effects, and the effects of whichever statement comes first change the conditions under which the subsequent ones are executed.

I didn't understand the 'read' statement that was listed with Boolean functions. Can you, for instance, say 'IF READ (X) THEN ...'? If so, when would READ (X) return a false value? Could you please give me an example of this usage of 'read'?

In Pascal, you would implement the read operation for Booleans as a procedure with the header

procedure ReadBoolean (var Source: Text; var Legend: Boolean;
  var Success: Boolean);
and you would invoke it in some such context as this:
Reset (Source, RosterFileName);
VoterNumber := 0;
while not EOF (Source) do begin
  VoterNumber := VoterNumber + 1;
  ReadString (Source, Roster[VoterNumber].Name);
  ReadBoolean (Source, Roster[VoterNumber].Registered, Success);
  if not Success then begin
    WriteLn ('Error in line ', VoterNumber: 1, ' of source file:');
    WriteLn ('Incorrectly formatted value in Registered field')
  end
  else { ... }
The error messages might appear if the character following the voter's name in the roster file was neither T for true nor F for false.

The handout on characters says that by adding nine leading zeros to an ASCII code, one gets the equivalent Unicode character. Does this only cover the alphabet, or does it include the entire ASCII table?

It includes the entire 128-character ASCII set.

Does each Unicode script have its own punctuation marks or is there one common set which covers punctuation for all scripts?

Not all scripts use punctuation. Among those that do, a few have simply adopted the punctuation marks used in the Latin script, but most have their own punctuation marks.

It was mentioned that some manufacturers use 8-bit ASCII. Is there a standard for it?

There are various competing versions of eight-bit ASCII, some of which are arguably standard, but none has been accepted as widely as the seven-bit ASCII set.

Does ASCII have any way for dealing with accented letters, such as one would find in most European languages? If not, is there a European equivalent to ASCII or does each language pretty much have to create its own standard?

Various organizations and computer manufacturers have proposed extensions to ASCII in which the eighth bit in each byte is used to make it possible to represent 128 additional characters. Unfortunately, these schemes are not consistent either from language to language, or from country to country, or from manufacturer to manufacturer.

You mentioned that there is no standard for an 8-bit ASCII code. Is ISO-Latin-1 an attempt at a standard, or a stopgap, or just a commonly used option? Does this have anything to do with the general decay of standards recently (e.g. many companies introducing proprietary HTML tags)?

ISO-Latin-1 is a standard (``ISO'' is the International Standards Organization), just one that hasn't been accepted as widely as ASCII.

I don't perceive a ``general decay of standards'' -- it's always been difficult to get people to accept and observe them, even when it is demonstrably to everyone's advantage. In computing, at least, standards that have failed to achieve widespread acceptance are far more common than success stories like seven-bit ASCII. Most computer professionals share the cynical attitude expressed in Andrew Tenenbaum's dictum ``The nice thing about standards is that there are so many of them to choose from.''

Does Chr (0) have a standard glyph, or do we always refer to it as Chr (0)?

There's no glyph for it in the Latin script. Some display devices show it as ^@, reflecting the fact that on many keyboards, including the HP's, you can generate it by pressing <Control/@> (that is, <Control/Shift/2>). Unicode calls it null, and the conventional ASCII abbreviation for it is NUL. I usually call it ``the null character.'' It's a control character, but on many devices it is implemented as having the interesting effect of doing absolutely nothing.

I don't doubt that Unicode is necessary for inclusiveness of the world's many languages, but do you think that it will be difficult to find what you want quickly with such a large set of characters? It seems a bit unwieldly to me.

The theory is that special-purpose editors or editing modes will be developed that facilitate the creation and processing of files of Unicode characters for users of particular scripts. You'll fire up your Tamil editor when you want to write in some language that uses Tamil, put Tamil keycaps on your keyboard, and proceed to type. The editor will look up and store the appropriate Unicode characters in the file that it creates.

What is the purpose of all of those little functions in the assigned handout? What do they demonstrate?

They show how to define names for the non-graphic ASCII characters in a portable way. For instance, if you want to refer to the ASCII escape character, the expression Escape is a lot clearer than Chr (27); but you can't write

const
    Escape = Chr (27);
in standard Pascal, because function calls are not allowed in constant definitions. You could make Escape a variable of type Char, but then you have to remember to initialize it before it is used and never to change it. Writing a zero-argument function with the name Escape allows you to write things like

if Source^ = Escape then
  Get (Source)
just as if Escape were a constant, but without violating the Pascal standard.

What is a negative acknowledge character used for?

It might be used in a system for sending data over a noisy line. After transmitting some fixed number of bytes, the sender inserts a checksum; the receiver also generates a checksum from the data as received and compares it with the sender's checksum. If they match, the receiver sends back the acknowledge character, and the sender proceeds to the next group of bytes; if they don't match, the receiver sends negative-acknowledge, and the sender repeats the same group of bytes.

On the HPs, does the <Enter> key return two nonprintable characters, like form-feed and carriage-return? I remember you saying different machines do different things when the <Enter> key is pressed.

Strictly speaking, the HP keyboard doesn't generate ASCII characters at all; the signals produced by pressing and releasing the keys are in a completely different code at the level of hardware. (For example, the <Enter> key on the typewriter-layout part of the keyboard and the <Enter> key on the numeric keypad have different hardware keycodes.) The software that mediates the keyboard's interactions with a running Pascal program converts hardware keycodes into ASCII characters.

The HP operating system uses the ASCII line-feed character as a line terminator when text files are stored. In displaying text on screen, the HP terminal emulator places the two-character sequence carriage-return line-feed at the end of each line -- the carriage-return to move the cursor to the left edge of the display and the line-feed to drop it down a line. Still other characters are generated if the text already displayed has to be scrolled upwards in order to make room for the new line.

How can I generate an ASCII form-feed character from the keyboard? How can I insert one into a text file?

From the keyboard, press <Control-L>. In XEmacs, press <Control-Q> <Control-L>. You may want to write a small Pascal program just to generate appropriate test data; to have Pascal write a form-feed to a text file, use Page (DataFile) or Write (DataFile, Chr (12)).

In the handout you say that Pascal can accommodate EBCDIC, which has gaps between consecutive alphabetical letters. When you have a case such as this, what happens to the Succ and Pred functions? Do they just produce a garbage character?

Exactly. On an EBCDIC machine, for instance, Pred ('J') is '}' (the right brace or right-curly-bracket).

Why does EBCDIC have gaps between letters? This seems silly to me.

Because the character codes were chosen to make the translation between the pattern of punches representing a character on a punched card and the bit pattern stored in memory as simple, straightforward, and efficient as possible.

A punched card of the era in which EBCDIC was designed could be punched in any of 960 positions, arranged in a rectangle of eighty columns and twelve rows. The rows were conventionally numbered (from top to bottom) as 12, 11, 0, 1, 2, 3, 4, 5, 6, 7, 8, and 9; usually the row numbers 0 through 9 were displayed at every punch position across the card, but rows 12 and 11 were left unprinted.

To store a character in one column of a punched card, one would punch out a particular combination of rectangular holes in that column. Digit-zero through digit-nine were represented by single punches in the correspondingly numbered column. Capital letters were represented by pairs of punches, one in row 12, 11, or 0 (at the top of the card) and the other in one of the rows 1 through 9. (Choosing rows that were far apart made it less likely that the card would be torn or otherwise damaged between punches in adjacent rows.) Capital-letter-a was 12-1 -- that is, holes were made in rows 12 and 1 of one column on the card to represent this letter. Capital-letter-b was 12-2, capital-letter-c was 12-3, and so on to capital-letter-i, which was 12-9. Then capital-letter-j was 11-1, capital-letter-k was 11-2, and so on to capital-letter-r, 11-9. Finally, capital-letter-s was 0-2 (avoiding 0-1 because they were adjacent rows), capital-letter-t was 0-3, and so on to capital-letter-z, 0-9.

When these punches were translated into EBCDIC character codes, the designers decided to have the last four bits of the EBCDIC character indicate which of the rows 1 through 9 was punched out, with 0001 for a punch in row 1, 0010 for a punch in row 2, 0011 for a punch in row 3, and so on up to 1001 for a punch in row 9. The first four bits would be set to 1100 for a punch in row 12, to 1101 (less logically) for a punch in row 11, and to 1110 for a punch in row 0. So the EBCDIC code for capital-letter-i is 11001001 -- 1100 for the 12-punch, 1001 for the 9-punch.

However, this leaves a numerical gap between capital-letter-i (11001001) and capital-letter-j (11010001), and another between capital-letter-r (11011001) and capital-letter-s (11100010). The designers of EBCDIC had a mildly plausible scheme for filling in these positions with characters that had more exotic punch combinations. For instance, right-curly-bracket was punched (on some IBM card punches, anyway) as 11-0, so it was natural to put it right before capital-letter-j (11-1).

This is probably more than you wanted to know. The real point is that the designers had a sensible motive that became obsolete when punched cards ceased to be a viable input medium.

I'm not quite clear on what the difference is between the letters being arranged in traditional alphabetical order and the letters being adjacent. How can they be in alphabetical order if they have other characters thrown in between?

In EBCDIC, the letters within either case (capitals or lower case) are in alphabetical order with respect to one another -- that is, if one letter precedes another alphabetically, then that letter precedes the other in EBCDIC as well. This is enough to guarantee that lexicographic sorting will work, provided that it involves only letters (of the same case).

If you wish to convert an ASCII character into a 7-bit binary representation, what is the most efficient method in Pascal? It seems to me, since Pascal is so isolated from the hardware and the operating system, that one would have to use a case statement:

case ch of:
  'A' : write "1000001";
  'B' : write "1000010";
...
Is the most efficient way to accomplish this? And is it necessary to add the parity bit onto the representation--and if so, is the parity bit 0 or 7?

The parity bit is bit 7. Whether it's necessary to display the parity bit depends on what you're trying to achieve -- showing ASCII code equivalents or exhibiting the contents of memory.

The better way to write out the bitwise representation of an ASCII character is to exploit the fact that it can be deduced from the character's ordinal value:

procedure WriteCharAsBits (Ch: Char);
var
  OrdinalValue: Integer;
  Bits: packed array [0 .. 6] of Char;
  BitNumber: Integer;
begin
  OrdinalValue := Ord (Ch);
  for BitNumber := 0 to 6 do begin
    if Odd (OrdinalValue) then
      Bits[BitNumber] := '1'
    else
      Bits[BitNumber] := '0';
    OrdinalValue := OrdinalValue div 2
  end;
  for BitNumber := 6 downto 0 do
    Write (Bits[BitNumber] : 1)
end;
Is it possible for me to read in whatever is entered from the keyboard as a Char? If it is, then it seems like I can always avoid an crash caused by entering data in a type which is different from what it should be, by reading it in as characters and then transforming it into the type we need (or sending out an error message, if it's not in the right form). Is that right?

Yes, it is. The only reason for using Read and ReadLn to read in values of any type other than Char is that the transformation is sometimes rather difficult. Recovering a value of type Real from its string representation is especially tricky and requires a lot of thought about all the cases that can arise.

What are the 32-bit binary representations of ASCII characters?

When a full thirty-two-bit word is set aside for an ASCII character -- for example, when it is loaded into a register -- usually bits 31 through 7 of the word are cleared (that is, turned off, made zero) and the seven bits of the ASCII character proper, as described in the handout on characters, are stored in bits 6 through 0.

I am curious about how EOLn and EOF are stored in relation to one another in a text file that is ended with a carriage return in comparison to one that is not.

The byte-by-byte format of a text file varies from one computer and operating system to another; Pascal provides EOLn and EOF functions precisely to impose a layer of abstraction between the programmer and these differing implementations.

I'll give three examples of text file formats: the one the HPs use, the one the academic VAX uses, and the one that is used on IBM PCs and clones.

On the HPs, a text file is stored as an unstructured sequence of ASCII characters, and the ASCII line-feed is used as a line terminator. In this system, it is possible for a text file to end in the middle of a line: this simply means that the last character in the text file is something other than line-feed. There is no special character to signal the end of the file; instead, the operating system keeps track of the exact number of bytes stored in each file and refuses requests for additional characters after that number of bytes has been released.

On the VAX, a text file is stored as a sequence of ``line records,'' each beginning with a four-byte integer that indicates how many characters of text appear on the line. The text characters are then lined up after this initial count. No character acts as a line terminator or as a file terminator. It is impossible for a file to end in the middle of a line.

On PCs, a text file is stored as a sequence of ASCII characters, with the two-character sequence carriage-return, line-feed as a line terminator. When a text file is created, the bytes between the end of the file and the end of the storage block on the disk are all conventionally filled with the ASCII substitute character, and many text-file applications treat substitute as an end-of-file signal. In that case, a text file can in principle end in the middle of a line, if the last two characters preceding substitute are not carriage-return and line-feed.

Is there a way to peek at the next character of standard input? This feature seems like it would have made the last assignment much easier to code. A classmate told me that Input^ accomplishes this task, but he we always encountered problems in his program when he tried to utilize this feature. If you can peek at standard input, what are the limitations/perils of doing that?

Your classmate is correct; Input^ returns the next character from standard input, without actually advancing past it. (A subsequent call to Read will still pick up that character.)

There are two main limitations of this method. (1) If input is arriving from the keyboard, the evaluation of the expression Input^ may ``hang'' the program until the user actually types in the character that has to be inspected in order for the expression's value to be determined. (2) You cannot detect the end of the file by checking to see whether the value of Input^ is the end-of-file character. There is no end-of-file character in ASCII. (In an interactive Pascal program under HP-UX, the user can signal the end of interactive input by pressing <Control/D> at the beginning of a line, but there is absolutely no way for the program to detect this character -- it is stripped out before the input is submitted to the program. If input is redirected so that it comes from a file, <Control/D> is not involved at all; instead, HP-UX just keeps track of the number of characters in each file and refuses to give the program a character when they have all been read. So, in particular, <Control/D> is not an end-of-file character.)

Do you think enumerated types are more trouble then they are worth?

No, I like enumerated types and believe that they prevent more trouble than they cause. The alternative, which is used in languages that have neither enumerated types nor symbol types, is to define a lot of numerical constants, like this:

const
  Juggler = 0;
  HighPriestess = 1;
  Empress = 2;
  { ... }
  Universe = 21;
type
  MajorArcanum = Juggler .. Universe;
This is a little cumbersome, but the real problem with it is that one can then do arithmetic on the values of the data type, and it's too easy to start making mistakes by performing arithmetic operations that make no sense.

It is a lot of trouble to write input and output procedures for enumerated types, if one uses symbolic names for them, but one would have exactly the same trouble with the input and output of symbolic names if one used integer constants instead.

Is there an easier way to change the character '1' to the integer 1 than:

Ord('1') - 48  { 48 = Ord ('0') }
or, say, to change the string '12345' to the number 12345?

No. But if you write a procedure to do this, it will come in very handy in a lot of the Pascal programs you write. Start building a library of useful procedures and functions that can be reused with little or no change in many programs.

What kind of process does Pascal use to translate a series of digits into an integer value?

It examines the digit characters one by one, from left to right. It recovers the value of each digit character as indicated in the previous question, by applying Ord to the digit character and subtracting Ord ('0') from the result. It combines these individual digit values by multiplying each one by the appropriate power of ten. The basic loop looks roughly like this:

Value := 0;
while not AtEndOfNumeral do begin
  Ch := NextCharacterOfNumeral;
  Value := Value * 10 + (Ord (Ch) - Ord ('0'))
end;
That is: Adding one digit to the end of an integer involves multiplying its previous value by ten and adding the digit.

The actual Read procedure is more complicated than this, because it has to deal with leading spaces, the possibility of a sign before the numeral, the possibility that the numeral will be greater than MaxInt, and so on.

In the handout on numeration, how do the Evaluate and Express procedures work?

Evaluate works by accumulating the numeric value of the part of the numeral that it has so far inspected, processing each digit by multiplying the previous value of the accumulator by the base of numeration and adding in the numeric value of the new digit.

Express works by recovering the last digit of a given integer, expressing it as a character to be placed at the end of the numeral, and then using recursion to deal with an appropriately reduced integer that contains all but the last digit of the original.

What are some of the practical applications of understanding machine data types, for programmers?

If you understand the machine representations of the data types, you know their limitations and can program around them. Here are two examples of what can go wrong if you don't, from Peter G. Neumann's book Computer-related risks (Reading, Massachusetts: Addison-Wesley Publishing Company, 1995), pages 34 and 169:

During the Persian Gulf war, the Patriot [anti-missile defense] system was initially touted as highly successful. In subsequent analyses, the estimates of its effectiveness were seriously downgraded, from about 95 percent to about 13 percent (or possibly less, according to MIT's Ted Postal; see SEN [Software Engineering Notes] 17, 2). The system had been designed to work under a much less stringent environment than that in which it was actually used in the war. The clock drift over a 100-hour period (which resulted in a tracking error of 678 meters) was blamed for the Patriot missing the [S]cud missile that hit an American military barracks in Dhahran, killing 29 and injuring 97. ... A later report stated that the software used two different and unequal versions of the number 0.1 -- in 24-bit and 48-bit representations (SEN 18, 1, 25). (To illustrate the discrepancy, the decimal number 0.1 has as an endlessly repeated binary representation 0.0001100110011.... Thus two different representations truncated at different lengths are not identical -- even in their floating-point representations.) ...

In this section, we consider accidental financial mishaps ... One of the most dramatic examples was the $32 billion overdraft experienced by the Bank of New York (BoNY) as the result of the overflow of a 16-bit counter that went unchecked. (Most of the other counters were 32 bits wide.) BoNY was unable to process the incoming credits from security transfers, while the New York Federal Reserve automatically debited BoNY's cash account. BoNY had to borrow $24 billion to cover itself for 1 day (until the software was fixed), the interest on which was about $5 million. Many customers were also affected by the delayed transaction completions (SEN 11, 1, 3-7).

Learning about machine representations makes it less likely that you will be the unfortunate programmer responsible for a mistake like these.

Could you explain how to multiply binary numbers and give an example?

Sure. The multiplication table is very easy, of course: 0 * 0 = 0, 0 * 1 = 0, 1 * 0 = 0, 1 * 1 = 1. If you're calculating on paper, you can set out the work just as if you were working in decimal numeration. For instance, to multiply 42 by 23, you could write

                                    101010
                                   x 10111
                                   -------
                                    101010
                                   101010
                                  101010
                                      0
                                101010
                                ----------
                                1111000110
The only hard part is to get the carries right when adding up a long series of partial products.

A computer can do much the same thing, except that it's likely to keep a running total of the partial products instead of adding them all up at the end. Here's how the algorithms might look if they were written out in Pascal:

const
  WordSize = 32;
  WordSizeMinusOne = 31;
type
  Bit = 0 .. 1;
  Word = packed array [0 .. WordSizeMinusOne] of Bit;

procedure MakeZero (var W: Word);
var
  BitNumber: Integer;
begin
  for BitNumber := 0 to WordSizeMinusOne do
    W[BitNumber] := 0
end;

procedure Add (Augend, Addend: Word; var Sum: Word);
var
  BitNumber: Integer;
  Carry: Bit;
  ColumnSum: Integer;
begin
  Carry := 0;
  for BitNumber := 0 to WordSizeMinusOne do begin
    ColumnSum := Augend[BitNumber] + Addend[BitNumber] + Carry;
    Sum[BitNumber] := ColumnSum mod 2;
    Carry := ColumnSum div 2
  end
end;

procedure ShiftLeft (var W: Word);
var
  BitNumber: Integer;
begin
  for BitNumber := WordSizeMinusOne downto 1 do
    W[BitNumber] := W[BitNumber - 1];
  W[0] := 0
end;

procedure Multiply (Multiplicand, Multiplier: Word; var Result: Word);
var
  BitNumber: Integer;
begin
  MakeZero (Result);
  for BitNumber := 0 to WordSizeMinusOne do begin
    if Multiplier[BitNumber] = 1 then
      Add (Result, Multiplicand, Result);
    ShiftLeft (Multiplicand)
  end
end;
Actual processors use a lot of short cuts to speed up the process -- in particular, they do various parts of the computation in parallel instead of sequentially.

Why is division the most time-consuming operation?

Because almost none of the steps can be done in parallel; the computation of each bit of the quotient depends critically on the outcome of the computation of the previous bit.

For the Negative and Positive procedures in the integers handout, what happens if the integer is zero?

Both procedures return False in that case.

In the Zero function, you defined the result as comparing the argument to 0.0. This seems a bit silly to me -- it seems that round-off errors could easily make a result that's supposed to be zero return a value of false. Wouldn't a construction like Zero := Operand < Tolerance be more appropriate, where Tolerance is an appropriately defined (small) constant?

That's not a bad suggestion. It would be still better to make Tolerance an input to the function, and to allow for rounding errors in either direction:

near-zero
Input: operand and tolerance, both real numbers.
Output: result, a Boolean.
Preconditions: tolerance is not negative.
Postcondition: result is true if operand differs from 0.0 by an amount less than or equal to tolerance (in either direction), false if it differs by more.

  function NearZero (Operand: Real; Tolerance: Real): Boolean;
  begin
    { Assert (0.0 <= Tolerance); }
    NearZero := (Abs (Operand) <= Tolerance)
  end;
A similar NearEqual function, for determining whether two real values are equal, to within a specified tolerance, would also be useful.

Whether NearZero and NearEqual should replace Zero and = as implementations of the zero and equal operations, or whether it would be better to add them as additional primitive operations, is not so clear. I think that programmers would find it frustrating to discover that some values were both ``positive'' and ``zero,'' while others were both ``negative'' and ``zero,'' so I guess I'd favor the latter alternative.

Some of the functions described in the reading don't seem that useful -- the Zero function, for example. It would be just as clear to write

if A = 0 ...
as to write

if Zero (A) ...
so it seems to me that the function takes up unneccessary space without adding much clarity. Is there some other reason for this sort of function?

If the compiler recognizes the function, it may be able to generate more efficient code for the special case that the function deals with than for the expression it replaces. In the case of Zero, for instance, many processors have a special instruction that determines whether all the switches in a given register are off; a compiler can direct such a processor to place the argument A in a register and then execute the all-switches-off test. This may be more efficient than placing A in one register, zero in another, and testing whether the results are equal.

However, current Pascal compilers will actually generate more efficient code for the equality test than for a call to Zero, so you're probably right in thinking that the function is a little pointless. It's actually there just for pedagogical reasons: I want people to think about the abstraction first and the implementation second, rather than trying to guess prematurely (while designing the data type) what the target machine will do.

In the integers handout, what does Modulo do? How does this differ from the mod operation?

Modulo extends the mod operation. It is an error for the second operand of mod to be negative; if the second argument to Modulo is negative, it still returns a value between zero (inclusive) and the modulus (exclusive).

What exactly does the modulo operation do?

It determines which residue class the moduland is a member of. A residue class for a given modulus is a set of integers, all differing from one another by multiples of the modulus. The set of natural numbers can be exhaustively partitioned into a number of residue classes equal to the absolute value of the modulus; for instance, if the modulus is 3, the residue classes are {..., -10, -7, -4, -1, 2, 5, 8, ...}, {..., -9, -6, -3, 0, 3, 6, 9, ...}, and {..., -8, -5, -2, 1, 4, 7, 10, ...}.

Each residue class contains exactly one integer in the range lying between zero (inclusive) to the modulus (exclusive), which uniquely identifies the residue class and is the value returned by the modulo operation.

Note that it is negative only if the modulus is negative and does not evenly divide the moduland.

Why doesn't the specification for the integer module include a DeallocateInt procedure?

It's not in the description of the abstract data type because it's an operation on storage, not on integers. It's not listed on the assignment sheet because perhaps not everyone will want to define Int as a pointer type. However, you should certainly add it if you do use a pointer type.

Which method do the HPs use to store integers?

Twos-complement representations in thirty-two bits.

In the 2's complement method of storing integers, how does the computer distinguish between positive and negative? It looks like any byte could be either a small positive integer or a large negative integer, or vice versa.

The leftmost bit of the representation indicates the sign. If it is zero (off), then the number represented is either positive or zero; if the sign bit is one (on), then the number represented is negative.

I was reading a book on microprocessors of the late seventies, and it explained twos-complement encoding as such: Ones-complement is the result of switching all the bits in sign-magnitude encoding, and twos-complement is the result of taking ones-complement and adding one (doing any necessary carries). I don't remember it being this simple - is it, or is the author oversimplifying something he doesn't think is important?

Well -- the statement of it that you've cited here is a little confused. I'll try to clear it up.

Signed-magnitude, ones-complement, and twos-complement representations are three different systems for storing integer values that may be positive, zero, or negative. All three of them use identical bit patterns for every positive integer (up to MaxInt, which is the same in all three systems).

However, they store negative numbers differently, and one can explain the difference concisely by describing the way the negative (or additive inverse) of an integer is computed. In the signed-magnitude system, toggle the sign bit. In the ones-complement system, toggle every bit. In the twos-complement system, toggle every bit and then add one, carrying as necessary -- or, equivalently, toggle every bit to the left of the rightmost 1-bit.

When I explained this in class, I skipped over ones-complement representations, because they are now very rarely encountered and in my opinion just get in the way of the explanation. So it may have sounded as if finding the negative of a twos-complement integer was more complicated than ``taking ones-complement and adding one.'' But it's not complicated.

Also, this book discusses doing math on 32-bit representations of real numbers using eight-bit busses and registers. This seems to me so difficult as to not be worth doing. If doing math with real numbers was so important, why not make the tradeoff and use a bigger, more expensive, 32-bit chip?

Because if you had put a thirty-two-bit processor in an Apple II in 1983, you would have increased its cost by a factor of a hundred.

Why is it that VAXen allocate storage with the half-words ``backwards''? What advantages does this lead to?

The main advantage was that data storage on the VAX was more nearly compatible with data storage on earlier Digital machines. This is why that representation was chosen.

Has the method of representing integers changed much in the last ten years? Is it likely to change in the next ten?

Twos-complement representations were very common -- clearly in the majority -- ten years ago, are overwhelmingly common now, and will be even more common ten years from now. By then it will be difficult to find a functioning computer that does not use twos-complement representations.

However, the number of bits in a word changes more frequently. Ten years ago, if I recall correctly, every computer at the College used either a sixteen-bit word or an eight-bit word. Today, machines with thirty-two-bit words are commonplace. Ten years from now, I expect sixty-four-bit words to be standard.

On page 600 of the Walker textbook, he discusses word length and its effect on integer ranges. I understand the restrictions on the range for 16 bit machines, i.e. -32768 to 32767 or something similar, but I am confused by the next paragraph, when he states that a 32-bit machine can encode about 5 x 10^9 numbers. If I raise 2 to the 32nd power, I only get 4,294,967,296, which is not 5 x 10^9. Also, if one of those bits is a positive/negative flag, then the integer range would be even more limited. I do understand that he is not necessarily talking about integer ranges, but rather storage locations ... but I am wondering where 5 x 10^9 came from.

Your figure is correct; Walker's was a rough estimate. (It came from reflecting that 2^10 is a little more than 10^3, so 2^30 should be a fair amount more than 10^9, so 2^32, or 4 * 2^30, should be a fair amount more than 4 * 10^9; Walker made a guess about the ``fair amount'' that turned out to be a little high.)

Using one of the bits to indicate the sign does not reduce the range of representable values of type Integer, provided that none of the integers that are marked as ``negative'' is equal to any of the integers marked as ``positive.'' For instance, in the twos-complement representation used on the HPs, there are 2147483648 negative values and 2147483648 ``positive'' ones (including zero); since they are all different, the total number of values represented is still 4294967296.

In class in Monday you mentioned a datatype that can store an infinite amount of integers. How?

By allocating storage for an integer dynamically, using pointers to link together enough blocks of fixed size to hold all of the digits of the integer.

Are there physically little switches inside the computer turning on and off?

Yes, but the switches don't have moving parts like light switches. Modern computers are completely electronic, which means among other things that the two states of each switch are distinguished not by the physical positions of its components but by some electrical characteristic (low and high capacitance, for instance). Some early computers, however, were electro-mechanical and used mechanical switches operated by relays.

Why are bits in a byte or word numbered from right to left rather than left to right?

Because when a sequence of bits is used to represent a natural number, each bit position corresponds to a power of two (just as in decimal numeration each digit position corresponds to a power of ten). Bit 0 corresponds to 2^0, or 1 (it's the ``units place''), bit 1 to 2^1, or 2 (the ``twos place''), bit 2 to 2^2, or 4 (the ``fours place''), and so on. The bit number matches the exponent.

Walker talks about data being stored in separate bytes or words because computers often have an easier time dealing with whole bytes or words. The trade-off is wasted space. With memory being so cheap, is this a general trend in computing?

Yes. Packed structures are used much less now than when I began teaching computer science in 1983. Programmers now generally think of arranging the fields of a record to conserve storage as an optimization that one performs only on programs that are memory-intensive; formerly it was usual to take alignment problems into consideration whenever one wrote the definition for a record type.

Why is it that some machines can't easily access the individual bits as opposed to just a whole word and others can? Is overall speed greater if this accessing is made trickier? Or is this just an older design?

Partly it is a question of efficiency. If it takes the same amount of time to transfer one bit, eight bits, or thirty-two bits from memory to the processor or vice versa, why bother having a separate address for each bit or even each byte? Instead, just bring in the word containing the relevant byte and have the processor recover the part of the word that is needed.

A second consideration is the number of bits required for an address. Suppose you're designing a machine that can be equipped with 2^k bytes of internal memory. If each byte has a separate address, the addresses will themselves contain at least k bits. If each bit has a separate address, addresses will have to contain k + 3 bits apiece; if the machine is word-addressible, k - 2 bits will suffice (if a word is four bytes). This can make a difference in the complexity and cost of the circuitry.

Is there a way to precedurally determine MaxInt without already knowing how many bits are in that computer's word?

Of course:

program FindMaxInt (Output);
begin
  WriteLn ('MaxInt = ', MaxInt : 1)
end.
What exactly am I looking at when I view a file of integer using a pager such as more or less? (It looks like gibberish to me.)

When the file is created, each integer is copied to the file exactly as it exists in memory, as a thirty-two-bit twos-complement representation. The bits are stored in the file exactly as they are in memory.

When a text-oriented tool such as a pager takes hold of this file, it tries to deal with it as a sequence of lines, each consisting of ASCII characters and terminated by a line break -- on our systems, the line-feed character, Chr (10). It picks up bits from the file in groups of eight and identifies the ASCII character in each one. If the ASCII character happens to be a graphic, it displays that character; if it is a control character, the window performs the appropriate control operation -- e.g., 00000111 causes a beep, 00001010 causes the cursor to move to the beginning of the next line, and so on. The result is gibberish, because the bit patterns that were stored have nothing to do with ASCII characters.

Does the hexadecimal system of numeration serve us any useful purpose? Will we ever need to use this in programming, or is this just an example to help us understand what the computer goes through in processing numbers?

You'll need it. For example, you'll eventually be learning to use debugging tools that can display the values of pointers; conventionally, they are written out in hexadecimal numerals (because the bit pattern of an address enables the programmer to determine whether the storage accessed through the pointer is aligned on a word boundary). Also, programmers occasionally want to read assembly-language listings of their programs, to find out what exactly a particular machine is doing in a frequently executed loop; base-16 numeration is heavily used in such listings.

Are you allowed to enter a number in scientific notation if it is of the type Real in Pascal, or do you have to set up some procedure to convert the notation first?

The Read and ReadLn procedures can cope with scientific notation when reading in a value for a Real variable -- one more reason why one would prefer to use those built-in procedures instead of defining one's own.

Can you define a long real in Pascal like you can in C?

You can in HP Pascal; the LongReal type is equivalent to the double type in C, the Real type to float. Of course, LongReal is non-standard.

In implementations of Pascal that provide only one floating-point type, it is usually equivalent to C's double rather than C's float.

Would a program using fractions of inches be more accurate than a program using metric measurements, since metric is decimal and inches are commonly expressed in fractions of base two (1/16, 1/2, etc.)?

Yes, it would, if it never performed any operation that resulted in a fraction in which the denominator was not a power of two.

Is there a real number equivalent to MaxInt? What would happen, for example, if I tried to read in a real number whose exponent component would require more than eight bits of storage?

The nearest analogue of MaxInt is the greatest number that has an exact IEEE single-precision representation, 2^128 - 2^104; let's call this number BigReal. If the HP Pascal Read procedure encounters a numeral for a value slightly larger than BigReal, it will round the value down to BigReal; if it encounters a numeral for a value much greater than BigReal, it will ``round it up'' to the IEEE positive infinity. Other implementations of Pascal may crash when trying to read in the numeral for an outsized value.

Is real number storage standardized, or are there many ways of doing this, too?

Unfortunately, there are even more ways of representing real numbers than of representing integers. There is a standard, or rather a small family of closely related standards, that the Institute of Electrical and Electronics Engineers labored over for years. They have had some success in getting computer manufacturers to adopt this standard, but it still isn't as popular as ASCII for characters.

The Java programming language, which is rapidly increasing in popularity, requires that real numbers be represented according to the IEEE standard; if the hardware for a machine that runs Java programs does not represent reals in this way, it is supposed to provide a software simulation of IEEE reals when running Java programs. Possibly this will lead to greater acceptance of the IEEE standard in the next generation of machine designs.

How does the machine store a real number into memory? Does it just round it to a certain number of decimals and then store each digit of the number as an integer, or is there something more complicated that it does?

The story is more complicated; the handout on IEEE representations of reals gives the full account, but I can sum it up briefly by saying that a variant of scientific notation is used: A real number is represented as a coefficient times a power of two, a * 2^b. Part of the storage allocated for a real value is used for the coefficient, a, and the rest for the exponent b. Neither a nor b is stored in exactly the way an integer value is, though, because it turns out to simplify the circuitry that performs arithmetic operations on real numbers if slightly different conventions are used.

After reading Appendix B.2 in Walker's book, I am still unclear about how exponents in real numbers are stored -- in particular, how the machine differentiates between a negative and a positive exponent.

The numeration system that is used for storing exponents is a ``biased-magnitude'' system. Suppose, for example, that we're storing an HP Pascal Real in the IEEE single-precision format. The exponent for such a value can be any integer in the range from -126 to 127. A fixed ``bias'' of 127 is added to the exponent, and then the eight-bit binary numeral for the result is actually stored. So, for instance, the exponent 5 would be stored as 10000100, which is the eight-bit binary numeral for 132 -- the true exponent, 5, plus the bias, 127.

The bias in this case happens to have been chosen in such a way that the leftmost bit of the representation of the exponent is 1 if the exponent is positive, 0 if it is zero or negative, so you could use that bit to determine the sign of the exponent. In practice, it is almost never important to know the sign of the exponent without knowing its actual value.

Is the leftmost bit the least significant bit in the mantissa, or is it the mantissa's rightmost bit?

The rightmost, bit 0, is the least significant.

Why is the greatest number in exact IEEE single precision 01111111011111111111111111111111 and not 01111111111111111111111111111111?

When all the bits in the exponent field are turned on, the IEEE conventions is that no real number is represented; instead, such a bit pattern indicates that an operation on real numbers was attempted when one of its preconditions was not met. The second of the bit patterns shown above, for instance, might arise as the result of the evaluation of the Pascal expression 0.0 / 0.0.

My question is about the range of IEEE double-precision real numbers. I understand the origin of the lower limit, 2^-1074, since the exponent comes from -1022 and the 52 bits stored in the mantissa. I don't understand why the upper limit is not 2^1075.

Single-precision reals have a similar restriction on the upper limit (2^128 - 2^104). Why is it not 2^150?

If you consider only normalized representations of real values -- those that don't exploit the all-zeroes setting of the exponent field, and so have an implicit 1. at the left of the mantissa field -- the lower bound is actually 2^-1022 for double-precision representations and 2^-126 for single-precision ones. You can get closer to zero only by using unnormalized representations, with all zeroes in the exponent field and an implicit 0. at the left of the mantissa field. There is no analogue of unnormalized representations at the high end of the system, so you have to stop when you get to the largest available setting of the exponent and mantissa fields -- there is no way to shift the implicit binary point any farther to the right.

How widely accepted are the IEEE standards for real-number representation?

I'd guess that more than half of the machines that have Pascal compilers use some variation of IEEE single- or double-precision reals -- fewer if you consider Turbo Pascal to be a form of Pascal (I don't).

Why don't you consider Turbo Pascal to be a form of Pascal?

In the language of the Pascal standard, it is not ``a processor complying with the requirements of this standard'' -- it does not provide all of the standard procedures, does not recognize all of the standard syntax, and does not detect all of the violations that the standard requires it to report.

Also, the object-oriented extensions that Turbo Pascal now incorporates have fundamentally changed the computational model that it embodies. A Pascal computation is a sequence of operations performed by a processor on inert data; a Turbo Pascal computation is an interaction among active objects, each embodying a state. So successful programming in Turbo Pascal is quite different from successful programming in Pascal -- one designs objects rather than procedures, functions, and data types.

Is it very inefficient for the computer to have all of the special cases in the IEEE real representation system? I know that plugging things like error messages in so as not to waste the bit combinations when the exponent is 11111111 saves bit space, but does this slow the computer down at all? It seems like it would be easier to be able to perform the same operations on all patterns and to be able to treat them all as legal values. is this slowdown at all comparable with the greater number of combinations?

It's not obvious when you look at it for the first time, but the IEEE floating-point representations are actually quite cleverly designed so that detecting and handling these special cases hardly slows the computation down at all. If you're going to use an exponent-and-mantissa representation at all, you have to treat 0.0 as a special case anyway; the marginal cost of handling unnormalized numbers as well is small. Similarly, in order to ``perform the same operations on all patterns and ... to treat them all as legal values,'' you need to have some way of handling the results of division by 0.0; reserving an easily recognized pattern of exponent bits for them is actually the most efficient way to do it.

Why do IEEE representations of real numbers use a biased exponent? Is there some way in which this improves efficiency at the circuit level?

Yes. Since all the biassed exponents are positive, you can find out whether one of them is greater than, equal to, or less than another without paying any attention to signs. Twos-complement and signed-magnitude representations do not have this property and therefore require more complicated algorithms (and hence circuits) for comparisons.

You subtracted one from the cardinality of the Real data type because -0 and 0 are the same. Why do we not do the same for the Integer data type?

Because only one of the 2^32 possible bit patterns is used to represent 0 as an integer; there is no separate bit pattern for a ``negative zero.'' In the Real type, the distinct bit patterns 00000000000000000000000000000000 and 10000000000000000000000000000000 are both used to represent 0.0.

Is the only thing that is unusual about longreal numbers the fact that they use L instead of E? That hardly seems worthy of calling them a different style of numeration.

That's the only difference in the numeration system that is used to represent LongReal values in HP Pascal source code. The internal representation of such values -- the fact that the IEEE double-precision representation rather than the IEEE single-precision representation is used -- is more consequential: It means that the range of LongReal values is much larger than the range of Real values, and that most LongReal values are stored to a precision of fifty-three significant binary digits rather than twenty-four.

Do real-number approximation problems (e.g., round-off error) become more or less pronounced as the number of bits used to represent them increases? In other words, are 32-bit reals less susceptible to round-off error than 16-bit reals?

You're probably meaning to contrast ``double-precision'' representations of real numbers (typically, sixty-four bits) with ``single-precision'' representations (typically, thirty-two bits). I don't know of any system that represents real numbers in sixteen bits.

Rounding errors are just as frequent with double-precision real numbers as with single-precision ones, but they tend to be much smaller, in the sense that the absolute value of the difference between the correct value and the value that is actually stored is less. For instance, the exact value of the single-precision representation of 7/5 is 11744051/8388608, which is too small by 1/41943040; the double-precision representation is 6305039478318694/4503599627370496, which is too small by 1/11258999068426240. A rounding error occurs in either case, but the distortion is less extreme if double-precision representations are used.

How significant can the errors which accumulate as the result of rounding errors be, when the lower limit of a real is 2^-149 and a longreal far, far greater? In what types of applications would such precision be required?

2^-149 is the least positive representable value of type Real, not the amount of rounding error in the representation of a typical real. The magnitude of the rounding error is proportional to the magnitude of the number represented; a normalized value of type Real can differ from the number it is trying to express by as much as 2^-24 times the magnitude of the value. So, for instance, if you're expressing the national debt of the United States (as I write, $5230766368737.51) as a real number of dollars, the value that is actually stored is 5230766325760.00 -- an error of almost forty-three thousand dollars.

In the postcondition for the round operation on real numbers, you say that if there are two integers that differ from operand by 0.5, result is the even one. In my understanding of rounding, one would round an operand differing from two integers by 0.5, not to the even integer, but to the larger.

Different authorities recommend different rounding policies. Standard Pascal's predefined Round procedure always rounds the halfway values away from 0.0, which may be what you had in mind as well. (If the operand is -2.5, is -3 or -2 the ``larger'' value? Standard Pascal rounds it to -3.) I see that I got this exactly wrong in the handout, so I'm glad to have the opportunity to make the correction.

The reason I recommend a round-to-even policy in the abstract data type is that I think that it is less likely to produce accumulations of rounding errors that are all in the same direction. In many applications, positive values with a fractional part of 0.5 occur frequently; rounding a long series of such values and then finding the sum can produce a very large rounding error, exaggerated by the fact that all the rounding is in the same direction (upwards). The round-to-even policy will produce rounding errors that tend to cancel each other out in such circumstances.

Walker's text book suggests that to write a binary number in scientific notation we must move the radix point as far to the left as possible, so that it is just to the left of the first 1. This is different from what you lectured on in class as the IEEE representation -- you said that they should be written as 1. something rather than 0.1 something. Is this just a peculiarity of IEEE representation?

It's more a difference in perspective. Walker's book places the notional binary point to the left of the first 1, but claims that the exponent has a bias of 128. The handout I wrote places the binary point to the right of the first 1, but claims that the exponent has a bias of 127. You get the same result in either case, but I think it's easier to add the explanation of how unnormalized numbers work if you present the numeration system as I did.

All this binary stuff we've been doing makes me wonder...is it possible to create machines that are more than bistable (tristable, quadstable, etc.)? If there were three memory states in each bit, the processing power would be much greater. Is this kind of device even remotely feasible?

Yes, but there are two problems with multistable devices: speed and reliability. Binary switches can change state very quickly, and it's also comparatively easy and fast to determine the state of a binary switch. Some mechanical and electromechanical computers of the forties and early fifties, used decimal numeration internally, like old-fashioned adding machines and desk calculators.

According to Knuth, a ternary system of numeration ``was given serious consideration along with the binary system'' during the development of early electronic computers in 1945 and 1946, at the Moore School of Engineering; the somewhat greater complexity of the arithmetic circuitry is partially offset by the greater concision of the representation. (On the average, the number of ``trits'' in the ternary representation of a number is about 63% of the number of bits in its binary representation). Knuth concludes, ``Perhaps the symmetric properties and simple arithmetic of this [balanced ternary] number system will prove to be quite important someday -- when the `flip-flop' is replaced by a `flip-flap-flop' '' (Seminumerical algorithms, volume 2 of The art of computer programming, p. 192).

What is a structured variable? The definitions of different types of variables, such as buffered variables, confused me too.

A structured variable is a variable that has other variables as components -- an array variable or a record variable. In other words, the region of a computer's memory that an array occupies can be divided up into smaller regions, one for each element of the array, and the region that a record occupies can be divided into smaller regions, one for each field of the record.

The discussion of the several kinds of variables on pages 69-71 of the Cooper book boils down to the fact that there are only five ways to refer to storage locations in Pascal:

  1. By means of an identifier -- a declared variable or parameter (in which case it is an entire-variable);
  2. By attaching a subscript to an array variable (in which case it is an indexed-variable, one of two kinds of component-variable);
  3. By attaching a field name to a record variable, or, in the context of a with-statement, using the field name by itself (in which case it is a field-designator, the other kind of component-variable);
  4. By dereferencing a pointer (in which case the variable is an identified-variable); or
  5. By referring to the buffer of an open file (in which case the variable is a buffer-variable).
Assuming that we have the definitions and declarations

type
  IntArray = array [1 .. 10] of Integer;
  Direction = (North, NorthEast, East, SouthEast, South, SouthWest,
               West, NorthWest);
  Weather = record
              Temperature: Real;
              WindSpeed: Real;
              WindDirection: Direction
            end;
  Access = ^Weather;
var
  Alpha: Integer;
  Vec: IntArray;
  Today: Weather;
  Tomorrow: Access;
  Target: Text;
and that Source has been opened for output, here is an example of each of the five kinds of variable:

  1. Alpha (entire-variable)
  2. Vec[3] (indexed-variable)
  3. Today.WindSpeed (field-designator)
  4. Tomorrow^ (identified-variable)
  5. Target^ (buffer-variable)
Although the same symbol is used to dereference a pointer and to refer to the buffer of a file, this is purely coincidental (not to say sloppy design) -- the two notions are unrelated.

Note that these are exactly the kinds of expressions that can appear on the left-hand side of an assignment statement -- in that position, you're referring to a storage location, so you need a ``variable-access'' expression.

Note also that although a set in Pascal is a structured value, a set variable is not a structured variable -- there's no way to refer to any part of the storage location that is occupied by a set, or to adjust a single element of a set without touching the rest of it. Another way of stating the same point is to say that not all data structures correspond to storage structures.

In what kind of situation would using arrays of more than two dimensions be appropriate? I could see using a three-dimensional array to model a 3-d board game. What about four-dimensional arrays? Are there any real-world problems that use this?

The number of dimensions in an array reflects the number of independent ways of classifying the objects that the array elements count or describe. For instance, an clothing store's inventory program might keep track of the number of men's slacks on hand by updating the elements of a five-dimensional array of the type defined below:

type
  Waist = 30 .. 42;
  Inseam = 28 .. 36;
  Weight = (Light, Medium, Heavy);
  Fly = (Button, Zipper);
  Color = (Blue, Brown, Black, Gray, Green, Tan);
  SlacksInventory = array [Waist, Inseam, Weight, Fly, Color] of Integer;
Is there some sort of general rule for when it's best to use arrays, as opposed to linked lists, for data storage?

Yes. It's better to use an array when you need ``random access'' to elements of the structure -- that is, when the order in which the program will examine those elements is unpredictable -- and when you either know in advance how many elements the structure will contain or can at least fix an upper bound that is likely to be approximately correct and certain not to be exceeded. It's better to use a linked list when the elements of the structure will usually be accessed sequentially, from first to last, or when almost all the accesses will involve a few identifiable elements (which can be moved to the front of the list); and when the number of elements in the structure will not be known, even approximately, until the program is running.

Why not implement arrays in such a way that subscripts could be added to them after they are created initially? That way they could grow in the same way as pointer structures, but wouldn't have pointers flying all over.

That's a good idea, and there are several languages -- C++, Java, Common Lisp, and Icon for instance -- in which you can do exactly that. Of course, they all use dynamically allocated structures hooked together with pointers internally, but the details aren't visible to the programmer. But such a data type goes against Pascal's design philosophy of staying close enough to the real machine that the student programmer is directly aware of the mechanics of storage allocation.

The reading for today discussed memory storage of arrays. However, isn't the storage scheme different for different implementations of Pascal? More importantly, isn't it different for diffferent computers?

Some of the details (such as alignments) are different. The general scheme of using base addresses and computed offsets is surprisingly uniform.

Why isn't there a term for ``word-aligned''? It seems useful to define that to me, just because it would be less cumbersome to say that something is word-aligned than 4-byte or 8-byte aligned, depending on machine architecture.

The phrase `word-aligned' is sometimes used. The HP documentation uses `2-byte-aligned', `4-byte-aligned', and `8-byte-aligned' so as not to mislead programmers who are trying to migrate onto HPs from machines on which the word size is different.

What's the point of calling something bit-aligned? You can't set half a bit.

True, but that just means that bit-alignment is the least restrictive of all alignment possibilities, not that it's a pointless concept. It still contrasts with byte-alignment, 2-byte-alignment, and so on. (A value in storage is bit-aligned if it can begin at any bit position within any byte; sometimes this is an important thing to know about it.)

I suppose the complaint is that there's nothing that a bit-aligned value need really be aligned with and no possibility for it to be somehow misaligned (overlapping the boundaries between bits). OK, so perhaps it's a misnomer.

What factors make a single bit in memory easier or harder to access? You gave examples about what makes single bytes easier and harder to access in class on Friday; is the answer to this just an extension of those factors?

Not quite. To recover the value of a single bit from memory, a processor will transfer the contents of smallest independently addressable unit of memory that contains that bit into a register, then ``mask off'' (that is, set to zero) all the other bits in the word, and finally shift the surviving bit rightwards in the register until it becomes the least significant bit. The masking operation takes the same amount of time regardless of where the bit is within the register; on some machine architectures, however, the shifting operation takes longer if the bit starts out farther to the left, so that the leftmost bits are the hardest ones to access.

When declaring variables in our programs, is it important to be thinking about byte alignment and about in what order we declare our variables?

It wouldn't hurt. The commonest situation in which it's really important to think about alignment is when you're writing the type definition for a large array of records; laying out the record in such a way as to minimize padding can make a big difference in the number of bytes required for the array.

How exactly are packed arrays implemented? I was reading your code to get the machine representation of a datum, and you declared a packed array of bits for the machine's unit. Does this mean that packing crams things together as close as possible, and you have to do bitwise operations on registers to recover the part of the machine's word that you want?

Different implementations of Pascal handle the packed keyword differently. As I mentioned in class, Sun Pascal simply ignored it and stored ``packed'' arrays in exactly the same way as any other kind of array. HP Pascal tries to place several elements of a packed array into a single byte, if they will all fit, but will not store any element in such a way that it crosses a boundary between two bytes of memory; it will leave some of the bits in a byte unused if too few bits remain in a byte to accommodate another element. (However, HP Pascal also offers ``crunched packing,'' in which not even one bit may be left between elements, even if some elements have to be stored across byte boundaries.)

Array elements that have been packed more than one to a byte must indeed be extracted by means of bitwise operations before they can be operated on.

I believe I understand how normal arrays are stored in computers, but what about packed arrays? I imagine the answer is implementation-dependent, since some machines are byte-addressable and others word-addressable. Does one simply calculate the offset using the number of bits in an element rather than number of elements?

As I mentioned in class, some implementations of Pascal simply ignore the keyword packed and store all arrays in the same way. Under HP Pascal, however, it is possible for two or more small array elements to be packed into the same word or even into the same byte. The compiler decides how many elements to pack into one unit of storage (the packing factor). When the compiler then translates a reference to an element of the array, its computation of the offset to be added to the base address includes an extra step; after figuring the difference between the value of the array index and the lower bound of the index type, the compiler divides by the packing factor, keeping both the quotient and the remainder. The quotient is the offset; it is added to the base address to obtain the address of the unit of storage that contains the array element. The remainder indicates the position of the element within that storage location; the compiler uses the remainder to figure out how, after moving the contents of the storage location into a storage register, it should mask and shift the bits to strip away all the irrelevant data stored along with the desired element.

Walker makes it seem as though every single word of storage space has an individual address, like an enormous array. Is this true? Why then don't we simply address the array in the same way the computer does, with the offset being something that we must simply know?

In some programming languages, you can do exactly that. In fact, Pascal is one of them -- a pointer in Pascal is simply an index into the memory considered as an array. This is disguised, in Pascal, by the fact that the language doesn't allow you to write out a string representation of a pointer value or to perform any arithmetic operations on it. In some other languages, such as C and Bliss, there are no such restrictions.

However, the picture of memory as an array of bytes or words, indexed by natural numbers, is not quite correct for some computers, such as IBM PCs and their clones. A memory location on the PC is identified by a combination of two indices: a sixteen-bit ``segment'' value, which identifies some contiguous group of 65536 bytes in the PC's memory, and a sixteen-bit ``offset,'' identifying one particular byte within the segment. Unfortunately, the segments are overlapping rather than mutually exclusive, so that a particular byte of memory can have many different but equivalent addresses -- byte 0 of segment 28 is the same physical collection of eight switches as byte 48 of segment 25, for instance.

In C, array subscipts must start with 0. I thought that this would save a lot of computing time, but in lecture you showed that arrays with subscripts starting from non-zero numbers are negligably harder to initialize than those starting with subscipts of zero. Why does C choose to do this? It seems very inconvenient.

If the subscripts for an array start with 0, the base address of the array and its virtual origin are equal and interchangeable. This simplifies all the computations that are done with the addresses of elements of the array. In Pascal, all the address computations are performed inside the compiler and the author of the compiler is expected to deal with any complications. In C, on the other hand, it is possible to determine the address of any variable or array element and store it in a pointer variable, and also to operate arithmetically on pointer values. The designers of C decided that the extra flexibility offered by array types in which the indices started at some non-zero value was not worth the extra trouble that would be caused by making the distinction between base addresses and virtual origins visible to the programmer.

If I have a packed array and I want to fill it with blanks, can I use the assignment

type
  X = array [1 .. 14] of Char;
var
  Y: X;

{ ... }

Y := '              '
or do I need to specify each character separately?

The only thing standing in the way of the correctness of the assignment is that the string of spaces is a packed array of characters, while the variable Y is just an array of characters. Put the keyword packed into the definition of type X and the assignment will be correct.

When I converted a string into a value of an enumerated type, I used a long if-statement. The string is defined to be 14 characters long. Since some of the strings I wanted to convert were not so long, I put spaces in the rest of the positions of the array. Since strings can be compared only if they have the same length, I also added spaces to the string constants with which I was comparing the input strings. It looked stupid. So instead I tried to pad the arrays with null characters (Chr (0)), deleting the space from the string constants in the if-statement. But this attempt failed. Is my original method the only way to solve this problem or is there a better solution?

You can compare two strings only if they have the same length; all the characters of a string are included in this length, even null characters, so padding with null characters is no better than padding with spaces -- in fact, it only makes things more difficult, since there is no way to include a null character in a string constant.

The best way to do this in standard Pascal is to write a special comparison procedure that allows strings of different lengths to be compared. The following function returns True whenever it is given two strings that are alike except possibly for different numbers of trailing null characters:

function EqualAsStrings (
  LeftOperand: packed array [LeftLow .. LeftHigh: Integer] of Char;
  RightOperand: packed array [RightLow .. RightHigh: Integer] of Char):
    Boolean;
var
  Position: Integer;
  EqualSoFar: Boolean;
begin
  Position := 0;
  EqualSoFar := True;
  while EqualSoFar and (Position < LeftHigh) and
                                (Position < RightHigh) do begin
    Position := Position + 1;
    if LeftOperand[Position] <> RightOperand[Position] then
      EqualSoFar := False
  end;
  while EqualSoFar and (Position < LeftHigh) do begin
    Position := Position + 1;
    if LeftOperand[Position] <> Null then
      EqualSoFar := False
  end;
  while EqualSoFar and (Position < RightHigh) do begin
    Position := Position + 1;
    if RightOperand[Position] <> Null then
      EqualSoFar := False
  end
  EqualAsStrings := EqualSoFar
end;
This procedure uses a feature of Pascal called ``conformant array parameters'' that you may have seen only briefly, in the Cooper book. I'll discuss conformant array parameters in more detail a little later in the semester.

How does one call a procedure containing conformant array parameters?

There's nothing distinctive about the syntax of the call. The argument corresponding to the parameter must be an array in which the base type matches the base type of the parameter and the index type matches the type of the index constants in the parameter.

The only restriction is that if a conformant array parameter is also a value parameter (as opposed to a variable parameter), the corresponding argument may not itself be a conformant array parameter.

How does the compiler represent conformant arrays in memory? Or does the conformant array specification apply only to parameters passed to subroutines?

Only parameters can be conformant arrays; declared variables, both global and local, must have a fixed size.

When the compiler translates an invocation of a procedure or function that has a value parameter of a conformant array type, it deduces the size of the array that is needed from the type of the corresponding argument and writes out machine instructions that allocate the necessary amount of storage for the duration of the execution of the procedure or function. Some details of the suggested implementation of this method can be found on page 94 of the Standard Pascal user reference manual.

A variable parameter of a conformant array type is handled like any other variable parameter; during the execution of the procedure or function, it is an alias for the corresponding argument. Only the address of that argument is actually passed to the procedure or function.

The worst-case scenario for the quicksort is O(n^2). Obviously, O(n^2) is bad, but is it an efficient or an inefficient O(n^2) method (``efficient'' and ``inefficient'' being relative; O(n^2) is something to be avoided if possible). How would it compare to the other O(n^2) sorts such as the selection sort or the bubble sort?

The worst case of quicksort is bad even for an O(n^2) sort -- worse than selection sort (same number of comparisons, more data movements), though probably not quite as bad as bubble sort.

How does choosing a value that should be the middle speed up a quick sort?

Quicksort examines an element only once and moves it no more than once during each partitioning step on the part of the array that contains that element, so the number of partitions performed on any part of the array is the critical quantity in determining the algorithm's performance. If the pivot element is always in the middle of the range of values, so that the partitioning step always divides the array segment into two equal parts, no part of the array of size n will be partitioned more than lg n times. But if the pivot is always at one end or the other, then there will be some part of the array that always winds up in the larger partition and is therefore exposed to n partitioning steps.

When I was taught quicksort last year in CS1, we were told that one of the things that made quicksort so quick was the small number of swaps. In the Walker text, the example of Quicksort includes a number of calls to the Swap procedure in the two procedures on p. 421, CheckUp and CheckDown. Instead of Swap which presumably takes three assignment statements each time executed, wouldn't it be much faster (especially in a very long list) to instead store one extra Temp variable of type DataType, store the first element in the list in this, and have CheckUp and CheckDown make one assignment statement?

It might be somewhat faster. Of course, you'd also have to set up a variable of the array's index type to keep track of the ``hole'' in the array -- the position into which the next out-of-place item is to be moved.

Actually, the rendition of quicksort that I usually show to people wh