Pascal Programming/Arrays

From Wikibooks, open books for an open world
Jump to navigation Jump to search

An array is a structure concept for custom data types. It groups elements of the same data type. You will use arrays a lot if you are dealing with lots of data of the same data type.

Lists[edit | edit source]

Notion[edit | edit source]

In general, an array is a limited and arranged aggregation of objects, all of which having the same data type called base type or component type.[1] An array has at least one discrete, bounded dimension, continuously enumerating all its objects. Each object can be uniquely identified by one or more scalar values, called indices, along those dimensions.

Declaration[edit | edit source]

In Pascal an array data type is declared using the reserved word array in combination with the auxiliary reserved word of, followed by the array’s base type:

program arrayDemo(output);
type
	dimension = 1..5;
	integerList = array[dimension] of integer;

Behind the word array follows a non-empty comma-separated list of dimensions surrounded by brackets.[fn 1] All array’s dimensions have to be ordinal data types, yet the base type type can be of any kind. If an array has just one dimension, like the one above, we may also call it a list.

Access[edit | edit source]

A variable of the data type integerList as declared above, holds five independent integer values. Accessing them follows a specific scheme:

var
	powerN: integerList;
begin
	powerN[1] := 5;
	powerN[2] := 25;
	powerN[3] := 125;
	powerN[4] := 625;
	powerN[5] := 3125;
	
	writeLn(powerN[3]);
end.

This program will print 125, since it is the value of powerN that has the index value 3. Arrays are like a series of “buckets” each holding one of the base data type’s values. Every bucket can be identified by a value according to the dimension specifications. When referring to one of the buckets, we have to name the group, that is the variable name (in this case powerN), and a proper index surrounded by brackets.

Character array[edit | edit source]

Lists of characters frequently have and had special support with respect to I/O and manipulation. This section is primarily about understanding, as in the next chapter we will get to know a more sophisticated data type called string.

Direct assignment[edit | edit source]

String literals can be assigned to array[] of char variables using an assignment statement, thus instead of writing:

program stringAssignmentDemo;
type
	fileReferenceDimension = 1..4;
	fileReference = array[fileReferenceDimension] of char;
var
	currentFileReference: fileReference;
begin
	currentFileReference[1] := 'A';
	currentFileReference[2] := 'Z';
	currentFileReference[3] := '0';
	currentFileReference[4] := '9';

You can simply write:

	currentFileReference := 'AZ09';
end.

Note, that you do not need to specify any index anymore. You are referring to the entire array variable on the LHS of the assignment symbol (:=). This only works for overwriting the values of the whole array. There are extensions allowing you to overwrite merely parts of a char array, but more on that in the next chapter.

Most implementations of string literal to char array assignments will pad the given string literal with insignificant char values if it is shorter than the variable’s capacity. Padding a string means to fill the remaining positions with other characters in order meet a certain size. The GPC uses space characters (' '), whereas the FPC uses char values whose ordinal value is zero (#0).

Reading and printing[edit | edit source]

Although not standardized, read/readLn and write/writeLn usually support writing to and reading from array[] of char variables.

Very early Pascal compilers supported only this kind of human-readable IO:

Nuvola-inspired-terminal.svg Code:

program interactiveGreeting(input, output);
type
	nameCharacterIndex = 1..20;
	name = array[nameCharacterIndex] of char;
var
	host, user: name;
begin
	host := 'linuxnotebook'; // in lieu of getHostName()
	writeLn('Hello! What is your name?');
	readLn(user);
	
	write('Hello, ', user, '. ');
	writeLn('My name is ', host, '.');
end.

Crystal Clear app kscreensaver.svg Output:

Hello! What is your name?
Aïssata
Hello, Aïssata. My name is linuxnotebook.
Note, display will vary according to the padding algorithm explained above and longer inputs will be silently clipped to the maximum capacity of user. The user input has been highlighted, and the program was compiled with the FPC.

This works because text files, like the standard files input and output, are understood to be infinite sequences of char values.[fn 2] Since our array[] of char is also, although finite sequence of char values, individual values can be copied pretty much directly to and from text files, not requiring any kind of conversion.

Primitive comparison[edit | edit source]

Unlike other arrays, array[] of char variables can be compared using = and <>.

	if user <> host then
	begin
		write('Hello, ', user, '. ');
		writeLn('My name is ', host, '.');
	end
	else
	begin
		writeLn('No. That is my name.');	
	end;

This kind of comparison only works as expected for identical data types. It is a primitive character-by-character comparison. If either array is longer or shorter, an = comparison will always fail, because not all characters can be compared to each other. Correspondingly, an <> comparison will always succeed for array[] of char values that differ in length.

Note, the EP standard also defines the EQ and NE functions, beside many more. The difference to = and <> is that blank padding (i. e. #0 in FPC or ' ' in GPC) has no significance in EQ and NE. In consequence, that means using these functions you can compare strings and char arrays regardless of their respective maximum capacity and still get the naturally expected result. The = and <> comparisons on the other hand look at the memory’s internal representation.

Matrices[edit | edit source]

An array’s base type can be any data type,[fn 3] even another array. If we want to declare an “array of arrays” there is a short syntax for that:

program matrixDemo(output);
const
	columnMinimum = -30;
	columnMaximum =  30;
	rowMaximum =  10;
	rowMinimum = -10;
type
	columnIndex = columnMinimum..columnMaximum;
	rowIndex = rowMinimum..rowMaximum;
	plot = array[rowIndex, columnIndex] of char;

This has already been described above. The last line is identical to:

	plot = array[rowIndex] of array[columnIndex] of char;

It can be expanded to two separate declarations allowing us to “re-use” the “inner” array data type:

	row = array[columnIndex] of char;
	plot = array[rowIndex] of row;

Note that in the latter case plot uses row as the base type which is an array by itself. Yet in the short notation we specify char as the base type, not a row but its base type.

When an array was declared to contain another array, there is a short notation for accessing individual array items, too:

var
	curve: plot;
	x: columnIndex;
	y: rowIndex;
	v: integer;
begin
	// initialize
	for y := rowMinimum to rowMaximum do
	begin
		for x := columnMinimum to columnMaximum do
		begin
			curve[y, x] := ' ';
		end;
	end;
	
	// graph something
	for x := columnMinimum to columnMaximum do
	begin
		v := abs(x) - rowMaximum;
		
		if (v >= rowMinimum) and (v <= rowMaximum) then
		begin
			curve[v, x] := '*';
		end;
	end;

This corresponds to the array’s declaration. It is vital to ensure the indices you are specifying are indeed valid. In the latter loop the if branch checks for that. Attempting to access non-existent array values, i. e. by supplying illegal indices, may crash the program, or worse remain undetected thus causing difficult to find mistakes.

Note A program compiled with the GPC will terminate with
./a.out: value out of range (error #300 at 402a76)
if you are attempting to access a non-existent array item (the final hexadecimal number may vary). The FPC, however, by default ignores this. You need to specifically request errors to be detected either by specifying the ‑Cr command-line switch, or placing the compiler directive comment
{$rangeChecks on}
in your source code (before you are accessing any arrays, writing this once in your source code is enough).

Confer also chapter “Enumerations”, subsection “Restriction”.

Note, the “unusual” order of x and y has been chosen to facilitate drawing an upright graph:

	// print graph, note reverse `downto` direction
	for y := rowMaximum downto rowMinimum do
	begin
		writeLn(curve[y]);
	end;
end.

That means, it is still possible to refer to entire “sub”-arrays as a whole. You are not forced to write all dimension an array value has, given it makes sense.

Array data types that have exactly two dimensions are also called matrices, singular matrix.

Note In mathematics a matrix does not necessarily have to be homogenous, but could contain different “data types”.

Real values[edit | edit source]

As introduced in one of the first chapters the data type real is part of the Pascal programming language. It is used to store integer values in combination with an optional fractional part.

Real literals[edit | edit source]

In order to distinguish integer literals from real literals, specifying real values in your source code (and also for read/readLn) differs slightly. The following source code excerpt shows some real literals:

program realDemo;
var
	x: real;
begin
	x := 1.61803398;
	x := 1E9;
	x := 500e-12
	x := -1.7320508;
	x := +0.0;
end.

To summarize the rules:

  • A real value literal always contains either a . as a radix mark, or an E/e to separate an exponent (the in ), or both.
  • There is at least one Western-Arabic decimal digit before and one after the . (if there is any).
  • The entire number and exponent are preceded by signs, yet a positive sign is optional.
Note 0.0 accepts a sign, but is in fact (in mathematics) a sign-less number. -0.0 and +0.0 denote the same value.

As it has always been, all number values cannot contain spaces.

Limitations[edit | edit source]

The real data type has many limitations you have to be aware of in order to effectively use it. First of all, we want to re-emphasize an issue that was mentioned when data types were introduced: In computing real variables can only store a subset of rational numbers (ℚ).[2] That means, for example, you cannot store the (mathematically speaking) real number (ℝ) . This number is an irrational number (i. e. not a rational number). If you cannot write a number as a finite real literal, it is impossible to store it in a system using a finite amount of memory, such as computer systems do.

Fortunately, in EP three constants aide your usage of real values. minReal is the smallest positive real value. It conjunction with the constant maxReal, it is guaranteed that all arithmetic operations in produce, quote, reasonable approximations.[3] It is not specified what exactly constitutes a “reasonable” approximation, thus it can, for example, mean that maxReal - 1 yields as “an approximation” maxReal.[fn 4] Also, it is quite possible that real variables can store larger values than maxReal.

epsReal is short for “epsilon real”. The small Greek letter ε (epsilon) frequently denotes in mathematics an infinitely small (positive) value, yet not zero. According to the ISO standard 10206 (“Extended Pascal”), epsReal is the result of subtracting 1.0 from the smallest value that is greater than 1.0.[3] No other value can be represented between this value and 1.0, thus epsReal represents the highest precision available, but just at that point.[fn 5] Most implementations of the real data type will show a significantly varying degree of precision. Most notable, the precision of real data type implementations complying with the IEEE standard 754 format, decays toward the extremes, when approaching (and going beyond) -maxReal and maxReal. Therefore you usually use, if at all, a reasonable multiple of epsReal that fits the given situation.

Transfer functions[edit | edit source]

Pascal’s strong typing system prevents you from assigning real values to integer variables. The real value may contain a fractional part that an integer variable cannot store.

Pascal defines, as part of the language, two standard functions addressing this issue in a well-defined manner.

  • The function trunc, short for “truncation”, simply discards any fractional part and returns, as an integer, the integer part. As a consequence this is effectively rounding a number toward zero. trunc(-1.999) will return the value -1.
  • If this feels “unnatural”, the round function rounds a real number to its closest integer value. round(x) is (regarding the resulting value) equivalent to trunc(x + 0.5) for non-negative values, and equivalent to trunc(x - 0.5) for negative x values.[fn 6] In other words, this is what you were probably taught in grade school, or the first rounding method you learned in homeschooling. It is commonly referred to as “commercial rounding”.

Both functions will fail if there is no such integer value fulfilling the function’s respective definition.

There is no function if you want to (explicitly) “transfer” an integer value to a real value. Instead, one uses arithmetically neutral operations:

  • integerValue * 1.0 (using multiplicative neutral element), or
  • integerValue + 0.0 (using summing neutral element)

These expressions make use of the fact, as it was mentioned earlier as a passing remark in the chapter on expressions, that the expression’s overall data type will become real as soon as one real value is involved.[4]

Note It is not guaranteed that all possible integer values can be stored as real variables.[fn 7] This primarily concerns non-small values, but it is important to understand that the data type integer is the best choice to accurately store integral, whole-numbered values nonetheless.

Printing real values[edit | edit source]

By default write/writeLn print real values using “scientific notation”.

Borland Pascal defines a real value constant pi. It was adopted by many compilers.

Nuvola-inspired-terminal.svg Code:

program printPi(output);
begin
	writeLn(pi);
end.

Crystal Clear app kscreensaver.svg Output:

 3.141592653589793e+00
This output has been generated by a program compiled with the GPC (without forcing ISO standard compliance). The width may vary, but the general style
  1. sign, where a positive sign is replaced with a blank
  2. one digit
  3. a dot
  4. a positive number of post-decimal digits
  5. an E (uppercase or lowercase)
  6. the sign of the exponent, but this time a positive sign is always written and a zero exponent is preceded by a plus sign, too
  7. the exponent value, with a fixed minimum width (here 2) and leading zeros
is always the same.

While this style is very universal, it may also be unusual for some readers. Particularly the E notation is something now rather archaic, usually only seen on pocket calculators, i. e. devices lacking of enough display space.

Luckily, write/writeLn allow us to customize the displayed style.

For starters, specifying a minimum total width is legal for real parameters, too, but this also shows more digits:

Nuvola-inspired-terminal.svg Code:

program printPiDigits(output);
begin
	writeLn(pi:40);
end.

Crystal Clear app kscreensaver.svg Output:

 3.141592653589793238512808959406186e+00
Note that 40 refers to the entire width including a sign, the radix mark, the e and the exponent representation.

The procedures write/writeLn accept for real type values (and only for real values) another colon-separated format specifier. This second number determines the (exact) number of post-decimal digits, the “fraction part”. Supplying two format specifiers also disables scientific notation. All real values are printed using regular positional notation. That may mean for “large” numbers such as 1e100 printing a one followed by a hundred zeros (just for the integer part).

Let’s look at an example:

Nuvola-inspired-terminal.svg Code:

program realFormat(output);
var
	x: real;
begin
	x := 248e9 + 500e-6;
	writeLn(x:32:8);
end.

Crystal Clear app kscreensaver.svg Output:

           248000000000.00048828
Note, the entire width, including . and in the case of negative numbers -, is 32 characters. After the . follow 8 digits. Bear in mind the precise number, especially the fractional part, may vary.

In some regions and languages it is customary to use a , (comma) or other character instead of a dot as a radix mark. Pascal’s on-board write/writeLn procedures will, on the other hand, always print a dot, and for that matter – read/readLn will always accept dots as radix marks only. Nevertheless, all current Pascal programming suites, Delphi, FPC and GPC provide appropriate utilities to overcome this restriction. For further details we refer to their manuals. This issue should not keep us from continuing learning Pascal.

The output write/writeLn (and in EP also writeStr) generate will be rounded with respect to the last printed digit.

Nuvola-inspired-terminal.svg Code:

program roundedWriteDemo(output);
begin
	writeLn(3.75:4:1);
end.

Crystal Clear app kscreensaver.svg Output:

 3.8
Note, this rounding has to be distinguished from approximations arising from real’s limitations. It was verified that the computer used for this demonstration could indeed store precisely the specified value. The rounding you see in this particular case is not due to any technical circumstances.

Comparisons[edit | edit source]

First of all, all (arithmetic) comparison operators do work with real values. The operators = and <>, though, are particularly tricky to handle.

In most applications you do not compare real values to each other when checking for equality or inequality. The problem is that numbers such as ⅓ cannot be stored exactly with a finite series of binary digits, only approximated, yet there is not one valid approximation for ⅓ but many legit ones. The = and <> operators, however, compare – so to speak — for specific bit patterns. This is usually not desired (for values that cannot be represented exactly). Instead, you want to ensure the values you are comparing are within a certain range, like:

function equal(x, y, delta: real): Boolean;
begin
	equal := abs(x - y) <= delta;
end;

Delphi and the FPC’s standard RTS provide the function sameValue as part of the math unit. You do not want to program something other people already have programmed for you, i. e. use the resources.

Division[edit | edit source]

Now that we know the data type used for storing (a subset of) rational numbers, in Pascal known as real, we can perform and use the result of another arithmetic operation: The division.

Flavors[edit | edit source]

Pascal defines two different division operators:

  • The div operator performs an integer division and discards, if applicable, any remainder. The expression’s resulting data type is always integer. The div operator only works if both operands are integer expressions.
  • The, probably more familiar, operator / (a forward slash), divides the LHS number (the dividend) by the RHS number (the divisor), too, but a /-division always yields a real type value.[4] This is also the case if the fractional part is zero. A “remainder” does not exist for the / operation.

The div operation can be put in terms of /:

divisor div dividend = trunc(divisor / dividend)

However, this is only a semantic equivalent,[fn 8] it is not how it is actually calculated. The reason is, the / operator would first convert both operands to real values and since, as explained above, not all integer values can necessarily be represented exactly as real values, this would produce results potentially suffering from rounding imprecisions. The div operator on the other hand is a pure integer operator and works with “integer precision”, that means no rounding is involved in actually calculating the div result.

Off limits divisor[edit | edit source]

Note, since there is no generally accepted definition for division by zero, a zero divisor is illegal and will result in your program to abort. If your divisor is not a constant and depends on run-time data (such as a variable read from user input), you should check that it is non-zero before doing a division:

if divisor <> 0 then
begin
	x := dividend / divisor;
	// ...
end
else
begin
	writeLn('Error: zero divisor encountered when calculating rainbow');
end;

Alternatively, you can declare data types excluding zero, so any assignment of a zero value will be detected:

type
	natural = 1..maxInt;
var
	divisor: natural;
begin
	write('Enter a positive integer: ');
	readLn(divisor); // entering a non-positive value will fail

Some Pascal dialects introduce the concept of “exceptions” that can be used to identify such problems. Exceptions may be mentioned again in the “Extensions” part of this Wikibook.

Arrays as parameters[edit | edit source]

Arrays can be copied with one simple assignment dataOut := dataIn;. This requires, however, as it is customary with Pascal’s strong type safety, that both arrays are assignment-compatible: That means their base type and dimension specifications are the same.[fn 9]

Because calling a routine involves invisible assignments, writing general-purpose code dealing with lots of different situations would be virtually impossible if the entire program had to use one array type only. In order to mitigate this situation, conformant array type parameters allow writing routines accepting differing array dimension lengths. Array dimension data types still have to match.

Let’s look at an example program using this feature:

program tableDemo(output);

procedure printTableRows(data: array[minimum..maximum: integer] of integer);
var
	i: integer; // or in EP `i: type of minimum;` [preferred alternative]
begin
	for i := minimum to maximum do
	begin
		writeLn(i:20, ' | ', data[i]:20);
	end;
end;

A conformant-array parameter looks pretty similar to a regular array variable declaration, but the dimensions are specified differently. Usually, when declaring new arrays you provide constant values as dimension limits. Since we do not want constants, though, we name placeholder identifiers for the actual dimension limits of any array printTableRows will receive. In this case they are named minimum and maximum, joined by .. inbetween indicating a range.

minimum and maximum become variables inside the definition of printTableRows, but you are not allowed to assign any values to them.[fn 10] You are not allowed to declare new identifiers bearing the same name as the array boundary variables.

In Pascal all constants implicitly have an unambiguously determinable data type. Since our array limit identifiers are in fact variables they require a data type. The : integer indicates both minimum and maximum have the data type integer.

Note In a conformant array paramter, the short notation for nested arrays uses a ; to separate multiple dimensions, thus resembling a regular parameter list.

Once we have declared and defined printTableRows we can use it with lots of differently sized arrays:

var
	table: array[0..3] of integer;
	nein: array[9..9] of integer;
begin
	table[0] := 1;
	table[1] := 2;
	table[2] := 4;
	table[3] := 8;
	printTableRows(table);
	
	nein[9] := 9;
	printTableRows(nein);
end.

Delphi and the FPC (as of version 3.2.0 released in 2020) do not support conformant-array parameters, but the GPC does.

Logarithms[edit | edit source]

Special support[edit | edit source]

Prior the 21st century logarithm tables and slide rules were heavily utilized tools in manual calculations, so much so it led to the inclusion of two basic functions in Pascal.

  • The function exp exponentiates a number to the base , Euler’s constant. The value of exp(x) is equivalent to the mathematical term .
  • The function ln, short for Latin “logaritmus naturalis”, takes the natural logarithm of a positive number. “Natural” refers to again.

Both functions always return real values.

Introduction[edit | edit source]

Since the use of logarithms is not necessarily taught in all curricula, or you might just need a refresher, here is a short primer: The basic idea of logarithms is that all operations are lifted one level.

logarithm level
real level
basic logarithmic rules

On the lifted level many operations become simpler, especially if the numbers are large. For instance, you can perform a rather easy addition if you actually mean to take the product of two numbers. For this you have to “lift” all operands one level up: This is done by taking the logarithm. Pay particular attention to : On the logarithm level is a non-“logarithmized” factor, you only take the logarithm of

Once you are done, you have descend one level again to get the actual “real” result of the intended operation (on the underlying level). The reverse operation of ln is exp.[fn 11] To put this principle in Pascal terms:

x * y = exp(ln(x) + ln(y))

Remember, x and y have to be positive in order to be valid parameters to ln.

Application[edit | edit source]

Taking the logarithm and then exponentiating values again are considered “slow” operations and introduce a certain overhead. In programming, overhead means taking steps that are not directly related to the actual underlying problem, but only facilitate solving it. In general, overhead is avoided, since (at first) it takes us even farther away from the solution.

Nevertheless, logarithms can be used to overcome range limitations of the real data type if intermediate results are out of its range, but it is known that the final result will definitely be within the range of real again.

Nuvola-inspired-terminal.svg Code:

program logarithmApplicationDemo(output);
const
	operand = maxReal;
var
	result: real;
begin
	// for comparison
	writeLn(maxReal:40);
	
	result := sqr(operand);
	result := sqrt(result);
	writeLn(result:40);
	
	// “lift” one level
	result := ln(operand);
	
	result := 2.0 * result; // corresponds to sqr(…)
	result := 0.5 * result; // corresponds to sqrt(…)
	
	// reverse `ln`: bring `result` “back down”
	result := exp(result);
	
	writeLn(result:40);
end.

Crystal Clear app kscreensaver.svg Output:

 1.7976930000000000495189307440746532950903200318892038e+308
                                                         Inf
 1.7976929999999315921963138504476453672053533033977331e+308
The intermediate result of sqr in line 10 exceeds the range of real rendering any subsequent results invalid. In this particular implementation this situation is displayed as Inf (infinity). Since we know that reversing this operation by taking the principal square root results in a storable result again, we can perform the same operation with logarithms instead. The shown output was generated by the program being compiled with the GPC. The program was executed on a 64-bit platform with an FPU using 80-bit numbers.

As you can see, this goes to the detriment of precision. It is a compromise between fast operations, and “accurate enough” results.

The best solution is, of course, finding a better algorithm. The above demonstration is effectively , that is abs(x) (remember, squaring a number always yields a non-negative number). This operation’s result will stay in the range of real.

Tasks[edit | edit source]

All tasks, including those in the following chapters, can be solved without conformant-array parameters. This takes account of the fact that not all major compilers support them.[fn 12] Nonetheless, using routines with conformant-array parameters are often the most elegant solution.

Write a program that prints the following multiplication table:
    1    2    3    4    5    6    7    8    9   10
    2    4    6    8   10   12   14   16   18   20
    3    6    9   12   15   18   21   24   27   30
    4    8   12   16   20   24   28   32   36   40
    5   10   15   20   25   30   35   40   45   50
    6   12   18   24   30   36   42   48   54   60
    7   14   21   28   35   42   49   56   63   70
    8   16   24   32   40   48   56   64   72   80
    9   18   27   36   45   54   63   72   81   90
   10   20   30   40   50   60   70   80   90  100
The program may not contain any string literals (i. e. you may not just use ten writeLn). The generation of data and printing data shall be implemented by separate routines (these routine may not call each other).
The following is a straight-forward implementation.
program multiplicationTable(output);

const
	xMinimum = abs(1);
	xMaximum = abs(10);
	yMinimum = abs(1);
	yMaximum = abs(10);
	// NB: Only Extended Pascal allows constant definitions
	//     based on expressions. The following two definitions
	//     are illegal in Standard Pascal (ISO 7185).
	zMinimum = xMinimum * yMinimum;
	zMaximum = xMaximum * yMaximum;

Calculating the maximum and minimum expected value now (as constants) has the advantage that the compiler will emit a warning during compilation if any value exceeds maxInt.

The abs were inserted as a means of documentation: The program only works properly for non-negative values.

type
	x = xMinimum..xMaximum;
	y = yMinimum..yMaximum;
	z = zMinimum..zMaximum;
	table = array[x, y] of z;

Using z as the table array’s base type (and not just integer) has the advantage that if we accidentally implement the multiplication incorrectly, assigning out-of-range values will fail. For such a trivial task like this one it is of course irrelevant, but for more difficult situations deliberately constricting the allowed range can thwart programming mistakes. Do not worry if you just used a plain integer.

var
	product: table;

Note, the product variable has to be declared outside and before populateTable and printTable are defined. This way both routines refer to the same product variable.[fn 13]

procedure populateTable;
var
	factorX: x;
	factorY: y;
begin
	for factorX := xMinimum to xMaximum do
	begin
		for factorY := yMinimum to yMaximum do
		begin
			product[factorX, factorY] := factorX * factorY;
		end;
	end;
end;

It is also possible to reuse previously calculated values, make use of the fact that the table can be mirrored along the diagonal axis, or do other “optimization stunts”. The important thing for this task, though, is to correctly nest the for loops.

procedure printTable;
var
	factorX: x;
	factorY: y;
begin
	for factorY := yMinimum to yMaximum do
	begin
		for factorX := xMinimum to xMaximum do
		begin
			write(product[factorX, factorY]:5);
		end;
		writeLn;
	end;
end;

An advanced implementation would, of course, first determine the maximum expected length and store it as a variable, instead of using the hardcoded format specifier :5. This, though, is out of this task’s scope.[fn 14] It just should be mentioned hardcoded values like this one are considered bad style.

begin
	populateTable;
	printTable;
end.
The following is a straight-forward implementation.
program multiplicationTable(output);

const
	xMinimum = abs(1);
	xMaximum = abs(10);
	yMinimum = abs(1);
	yMaximum = abs(10);
	// NB: Only Extended Pascal allows constant definitions
	//     based on expressions. The following two definitions
	//     are illegal in Standard Pascal (ISO 7185).
	zMinimum = xMinimum * yMinimum;
	zMaximum = xMaximum * yMaximum;

Calculating the maximum and minimum expected value now (as constants) has the advantage that the compiler will emit a warning during compilation if any value exceeds maxInt.

The abs were inserted as a means of documentation: The program only works properly for non-negative values.

type
	x = xMinimum..xMaximum;
	y = yMinimum..yMaximum;
	z = zMinimum..zMaximum;
	table = array[x, y] of z;

Using z as the table array’s base type (and not just integer) has the advantage that if we accidentally implement the multiplication incorrectly, assigning out-of-range values will fail. For such a trivial task like this one it is of course irrelevant, but for more difficult situations deliberately constricting the allowed range can thwart programming mistakes. Do not worry if you just used a plain integer.

var
	product: table;

Note, the product variable has to be declared outside and before populateTable and printTable are defined. This way both routines refer to the same product variable.[fn 13]

procedure populateTable;
var
	factorX: x;
	factorY: y;
begin
	for factorX := xMinimum to xMaximum do
	begin
		for factorY := yMinimum to yMaximum do
		begin
			product[factorX, factorY] := factorX * factorY;
		end;
	end;
end;

It is also possible to reuse previously calculated values, make use of the fact that the table can be mirrored along the diagonal axis, or do other “optimization stunts”. The important thing for this task, though, is to correctly nest the for loops.

procedure printTable;
var
	factorX: x;
	factorY: y;
begin
	for factorY := yMinimum to yMaximum do
	begin
		for factorX := xMinimum to xMaximum do
		begin
			write(product[factorX, factorY]:5);
		end;
		writeLn;
	end;
end;

An advanced implementation would, of course, first determine the maximum expected length and store it as a variable, instead of using the hardcoded format specifier :5. This, though, is out of this task’s scope.[fn 14] It just should be mentioned hardcoded values like this one are considered bad style.

begin
	populateTable;
	printTable;
end.


Think of as many different ways to specify real value literals shorter than five characters, all denoting the value “positive one”. What are they?
Regardless of their usability all the following are legal real value literals denoting the value “positive one”:
  1. 1.0
  2. +1.0
  3. 1.00
  4. 1E0
  5. 1E+0
  6. 1E-0
  7. 1E00
In real life you primarily use of course 1.0. This exercise is meant to sensitize you to the fact that (unlike integer values) real number values can have many valid representations. Note, some compilers will accept literals such as 1., too, but this non-standard. The GPC will only accept it in non-ISO-compliant modes, but still emit a warning.
Regardless of their usability all the following are legal real value literals denoting the value “positive one”:
  1. 1.0
  2. +1.0
  3. 1.00
  4. 1E0
  5. 1E+0
  6. 1E-0
  7. 1E00
In real life you primarily use of course 1.0. This exercise is meant to sensitize you to the fact that (unlike integer values) real number values can have many valid representations. Note, some compilers will accept literals such as 1., too, but this non-standard. The GPC will only accept it in non-ISO-compliant modes, but still emit a warning.


Write a binary (= accepting/taking two parameters) integer function that calculates the n-th integer power of a positive number. Restrict the parameters’ data types as much as possible. The function should return 0 if the result is invalid (i. e. out of range).
Calculate means that we want the exact result. Although you have learned logarithms in this lesson, it is always best to stay within the realm of integers. Logarithms potentially yield approximations. The following is an acceptable implementation:
type
	naturalNumber = 1..maxInt;
	wholeNumber = 0..maxInt;

{**
	\brief iteratively calculates the integer power of a number
	\param base the (positive) base in x^n
	\param exponent the (non-negative) exponent in x^n
	\return \param base to the power of \param exponent,
		or zero in the case of an error
**}
function power(base: naturalNumber; exponent: wholeNumber): wholeNumber;
var
	accumulator: wholeNumber;
begin
	{ anything [except zero] to the power of zero is defined as one }
	accumulator := 1;
	
	for exponent := exponent downto 1 do
	begin
		{ if another “times `base`” would exceed the limits of `integer` }
		{ we invalidate the entire result }
		if accumulator > maxInt div base then
		begin
			accumulator := 0;
		end;
		
		accumulator := accumulator * base;
	end;
	
	power := accumulator;
end;
As you can see, it is perfectly valid to make such null statements as exponent := exponent just to satisfy the Pascal’s syntax requirements. A good compiler will optimize that away. Note that the EP standard provides the integer power operator pow.[fn 15]
Calculate means that we want the exact result. Although you have learned logarithms in this lesson, it is always best to stay within the realm of integers. Logarithms potentially yield approximations. The following is an acceptable implementation:
type
	naturalNumber = 1..maxInt;
	wholeNumber = 0..maxInt;

{**
	\brief iteratively calculates the integer power of a number
	\param base the (positive) base in x^n
	\param exponent the (non-negative) exponent in x^n
	\return \param base to the power of \param exponent,
		or zero in the case of an error
**}
function power(base: naturalNumber; exponent: wholeNumber): wholeNumber;
var
	accumulator: wholeNumber;
begin
	{ anything [except zero] to the power of zero is defined as one }
	accumulator := 1;
	
	for exponent := exponent downto 1 do
	begin
		{ if another “times `base`” would exceed the limits of `integer` }
		{ we invalidate the entire result }
		if accumulator > maxInt div base then
		begin
			accumulator := 0;
		end;
		
		accumulator := accumulator * base;
	end;
	
	power := accumulator;
end;
As you can see, it is perfectly valid to make such null statements as exponent := exponent just to satisfy the Pascal’s syntax requirements. A good compiler will optimize that away. Note that the EP standard provides the integer power operator pow.[fn 15]


Improve your previous solution and make it work for negative base values as well. If your compiler supports the EP procedure halt, your function should print an error message and terminate the program if , because there is no universally agreed definition for .
The changed lines have been highlighted.
{**
	\brief iteratively calculates the integer power of a number
	\param base the non-zero base in x^n
	\param exponent the (non-negative) exponent in x^n
	\return \param base to the power of \param exponent,
		or zero in the case of an error
	
	The program aborts if base = 0 = exponent.
**}
function power(base: integer; exponent: wholeNumber): integer;
var
	accumulator: integer;
	negativeResult: Boolean;
Remember to update all data types and keep the source code documentation in sync.
begin
	if [base, exponent] = [0] then
	begin
		writeLn('Error in `power`: base = exponent = 0, but 0^0 is undefined');
		halt;
	end;
As per task specifications this part was optional. Here, a set was chosen to sensitize you to that possibility. You will, nevertheless, usually and most probably write (base = 0) and (base = exponent) or similar, which is just as valid.
	{ anything [except zero] to the power of zero is defined as one }
	accumulator := 1;
	
	negativeResult := (base < 0) and odd(exponent);
	{ calculating the _positive_ power of base^exponent }
	{ simplifies the invalidation condition in the loop below }
	base := abs(base);
	
	if base > 1 then
	begin
		for exponent := exponent downto 1 do
		begin
			{ if another “times `base`” would exceed the limits of `integer` }
			{ we invalidate the entire result }
			if accumulator > maxInt div base then
			begin
				accumulator := 0;
			end;
			
			accumulator := accumulator * base;
		end;
	end;
The necessity of this new if branch may be not as apparent as it should be: Because we earlier extended the range of possible base values to all integer values, it has also become possible to specify 0. However, remember division by zero is illegal. Since our invalidation condition relies on div base we need to take precautionary steps.
	if negativeResult then
	begin
		accumulator := -1 * accumulator;
	end;
	
	power := accumulator;
end;
The changed lines have been highlighted.
{**
	\brief iteratively calculates the integer power of a number
	\param base the non-zero base in x^n
	\param exponent the (non-negative) exponent in x^n
	\return \param base to the power of \param exponent,
		or zero in the case of an error
	
	The program aborts if base = 0 = exponent.
**}
function power(base: integer; exponent: wholeNumber): integer;
var
	accumulator: integer;
	negativeResult: Boolean;
Remember to update all data types and keep the source code documentation in sync.
begin
	if [base, exponent] = [0] then
	begin
		writeLn('Error in `power`: base = exponent = 0, but 0^0 is undefined');
		halt;
	end;
As per task specifications this part was optional. Here, a set was chosen to sensitize you to that possibility. You will, nevertheless, usually and most probably write (base = 0) and (base = exponent) or similar, which is just as valid.
	{ anything [except zero] to the power of zero is defined as one }
	accumulator := 1;
	
	negativeResult := (base < 0) and odd(exponent);
	{ calculating the _positive_ power of base^exponent }
	{ simplifies the invalidation condition in the loop below }
	base := abs(base);
	
	if base > 1 then
	begin
		for exponent := exponent downto 1 do
		begin
			{ if another “times `base`” would exceed the limits of `integer` }
			{ we invalidate the entire result }
			if accumulator > maxInt div base then
			begin
				accumulator := 0;
			end;
			
			accumulator := accumulator * base;
		end;
	end;
The necessity of this new if branch may be not as apparent as it should be: Because we earlier extended the range of possible base values to all integer values, it has also become possible to specify 0. However, remember division by zero is illegal. Since our invalidation condition relies on div base we need to take precautionary steps.
	if negativeResult then
	begin
		accumulator := -1 * accumulator;
	end;
	
	power := accumulator;
end;


Write a program that accepts “chirps”, that is microblogging messages consisting of up to 320 characters.
  • The user will terminate his input with an empty line. Print this instruction beforehand.
  • When done, print the message.
  • When printing, a line may be at most 80 characters long (or whatever is reasonable for you). You are allowed to presume the user’s input lines are at most 80 characters long.
  • Ensure you only wrap lines at space characters (unless there are no space characters in a line).
Hint: For testing purposes you may want to scale down your input sizes.


More tasks you can solve can be found on the following Wikibook pages:


Sources:

  1. Jensen, Kathleen; Wirth, Niklaus. Pascal – user manual and report (4th revised ed.). p. 56. doi:10.1007/978-1-4612-4450-9. ISBN 978-0-387-97649-5. "An array type consists of a fixed number of components (defined when the array type is introduced) all having the same type, called the component type." 
  2. This limitation comes from Pascal: ISO 7185:1990 (Report). ISO/IEC. 1991. p. 16. "real-type. The required type-identifier real shall denote the real-type. […] The values shall be an implementation-defined subset of the real numbers, denoted as specified in 6.1.5 by signed-real."  The end of the last sentence implies only writable, those you can specify in your source code, are legal real values. For example, the value is different from , the decimial representation of which would be infinitely long (a. k. a. irrational number), thus the actual, correct value of cannot appear in source code as a “real” value.
  3. a b Joslin, David A. (1989-06-01). "Extended Pascal – Numerical Features". Sigplan Notices 24 (6): 77–80. doi:10.1145/71052.71063. "The programmer can obtain some idea of the real range and precision from the (positive) implementation-defined constants MINREAL, MAXREAL and EPSREAL. Arithmetic in the ranges [-maxreal,-minreal], 0, and [minreal,maxreal] “can be expected to work with reasonable approximations”, whereas outside those ranges it cannot. As what constitutes a “reasonable approximation” is a matter of opinion, however, and is not defined in the standard, this statement may be less useful than it appears at first sight. The measure of precision is on somewhat firmer ground: EPSREAL is the commonly employed measure of (typically floating-point) accuracy, i.e the smallest value such that 1.0 + epsreal > 1.0.". 
  4. a b Jensen, Kathleen; Wirth, Niklaus. Pascal – user manual and report (4th revised ed.). p. 20. doi:10.1007/978-1-4612-4450-9. ISBN 978-0-387-97649-5. "As long as at least one of the operands is of type Real (the other possibly being of type Integer) the following operators yield a real value:
    * multiply
    / divide (both operands may be integers, but the result is always real)
    + add
    - subtract
    "
     

Notes:

  1. Some (old) computers did not know the bracket characters. Seriously, that’s not a joke. Instead, a substitute bigram was used: var signCharacter: array(.Boolean.) of char;, and signCharacter(.true.) := '-';. You may encounter this kind of notation in some (old) textbooks on Pascal. Anyway, using brackets is still the preferred method.
  2. More precisely, text files are (possibly empty) sequences of lines, each line consisting of a (possibly empty) sequence of char values.
  3. Some compilers (such as the FPC) allow zero-sized data types [not allowed in any ISO standard]. If that is the case, an array that has a zero-size base type will be rendered ineffective, virtually forfeiting all characteristics of arrays.
  4. Modern real arithmetic processors can indicate a precision loss, i. e. when the result of an arithmetic operation had to be “approximated”. However, there is no standardized way to access this kind of information from your Pascal source code, and usually this kind of signaling is also not favorable, since the tiniest precision loss will set off the alarm, thus slowing down your program. Instead, if it matters, one uses software that allows arbitrary precision arithmetics, like for example the GNU Multiple Precision Arithmetic Library.
  5. This number is not completely arbitrary. The most prevalent real number implementation IEEE 754 uses a “hidden bit”, making the value 1.0 special.
  6. Not all compilers comply with this definition of the standard. The FPC’s standard round implementation will round in the case of equidistance toward even numbers. Knowing this is relevant for statistical applications. The GPC uses for its round implementation functionality provided by the hardware. As such, the implementation is hardware-dependent, on its specific configuration, and may deviate from the ISO 7185 standard definition.
  7. Given the most prevalent implementations Two’s complement for integer values and IEEE 754 for real values, you have to consider the fact that (virtually) all bits in an integer contribute to its (mathematical) value, whereas a real number stores values for the expression mantissa * base pow exponent. In very simple terms, the mantissa stores the integer part of a value, but the problem is that it occupies fewer bits than an integer would use, thus there is (for values that require more bits) a loss in information (i. e. a loss in precision).
  8. The exact technical definition reads like: The value of dividend div divisor shall be such that
    abs(dividend) - abs(divisor) < abs((dividend div divisor) * divisor) <= abs(dividend)
    
    where the value shall be zero if abs(dividend) < abs(divisor); otherwise, […]
  9. Furthermore, both arrays have to be either packed or “unpacked”.
  10. The EP standard calls this characteristic protected.
  11. It is important that both functions use one common base, in this case it is .
  12. The ISO standard 7185 (“Standard Pascal”) calls this, lack of conformant-array parameters, “Level 0 compliance”. “Level 1 compliance” includes support for conformant array parameters. Of the compilers presented in Getting started only the GPC is a “Level 1”-compliant compiler.
  13. This style of programming is slightly disfavored, keyword “global variables”, but as for now we do not know appropriate syntax (var parameters) to not do that.
  14. For extra credit: You can make use of the fact that (assuming that zMaximum is positive) . You can use this value to find out the minimum number of digits required.
  15. In EP there also exists a real power operator **. The difference is similar to that of the division operators: pow only accepts integer values as operands and yields an integer value, whereas ** always yields a real value. Your choice for either of which, again, should be based on the required degree of precision.

Next Page: Strings | Previous Page: Sets

Home: Pascal Programming