AS & A Level Computer Science · Area 2 · A Level content

User-defined data types, file organisation and floating-point numbers

This is the A Level data representation section: types you declare yourself, how records are arranged and found in a file, and how real numbers are held as a mantissa and an exponent. The ideas are not difficult, but the marks are precise, so you lose them by describing a pointer as a value, by calling a hashed file random because the records look shuffled, or by shifting a mantissa without changing the exponent the other way. Work through the conversions with a pencil until the bit patterns stop surprising you.

Not started

Syllabus coverage

  • Why user-defined data types are necessary
  • Define and use non-composite types: enumerated, pointer
  • Define and use composite data types: set, record and class/object
  • Choose and design an appropriate user-defined data type for a given problem
  • Methods of file organisation and selecting an appropriate method for a given problem: serial, sequential (using a key field), random (using a record key)
  • Methods of file access, including sequential access for serial and sequential files
  • Hashing algorithms, and describing and using them to read from and write data to a random or sequential file
  • Direct access for sequential and random files
  • The format of binary floating-point real numbers, using two's complement form
  • The effects of changing the allocation of bits to the mantissa and the exponent in a floating-point representation
  • Converting binary floating-point real numbers into denary and denary into binary floating point
  • Normalising floating-point numbers, and the reasons for normalisation
  • The consequences of a binary representation being only an approximation to the real number it represents
  • How underflow and overflow can occur, and how binary representations give rise to rounding errors

Part of the AS & A Level Computer Science syllabus 9618.

Why user-defined data types are necessary

Every language gives you a set of built-in data types - INTEGER, REAL, CHAR, STRING, BOOLEAN and DATE in 9618 pseudocode - and for much of your data they are enough. A user-defined data type is one you declare yourself, built from the built-in types and from other types you have already declared. You need one either because no built-in type can describe the data at all, or because a built-in type can describe it but cannot stop the program from using it wrongly.

The first reason is a fixed set of values. Some data has only a handful of legal values and no built-in type knows this. An order can be Pending, Packed, Dispatched, Delivered or Returned, and nothing else.

Stored as a STRING, the language will happily accept 'Dispatchedd', 'dispatched' and 'sent', and every comparison in the program will then fail silently. Stored as an INTEGER, it will happily accept 47. An enumerated type declares the five legal values once, so the program can hold nothing else.

The second reason is grouping. A pupil's details are a name, a date of birth, a form group, a set of subject choices and an average mark - five different types that mean one thing together. Without a composite type you would keep five parallel arrays, pass all five to every procedure that needs them, and keep them in step by hand. A record puts them under one identifier, so a single variable can be passed, copied, sorted and stored as a unit.

The third reason is maintenance and reuse. A named type documents itself: StudentRecord tells the next programmer far more than ARRAY[1:40] OF STRING. The definition sits in one place, so adding a field or widening a field changes every use of the type instead of every line of code that mentions it. A type designed for one program can be reused in another program that needs the same structure, which is how libraries of types and classes are built up.

The fourth reason is control over access. A class can keep its attributes private and let the rest of the program change them only through methods, so an account balance can change only through a deposit or a withdrawal that checks the amount. That prevents invalid states and hides how the data is stored, so the implementation can be rewritten without breaking the code that uses it. Pointers are the fifth reason: a structure that grows and shrinks while the program runs, such as a linked list, cannot be built from the built-in scalar types at all.

  • Fixed set of legal values: an enumerated type rejects anything not in the list, which a STRING or an INTEGER cannot do.
  • Grouping related data: a record or class holds fields of different types under one identifier, so they are passed and stored as one item.
  • Readability: a meaningful type name says what the data is, which matters in a program written by a team.
  • Maintenance: one definition, changed in one place, updates every use of the type.
  • Reuse: a type or class written for one program can be used in another that needs the same structure.
  • Controlled access: a class keeps attributes private and exposes methods, so the data cannot be put into an invalid state.
  • Dynamic structures: pointers allow memory to be linked and resized at run time, which fixed built-in types cannot do.
The mark for why user-defined types are necessary is earned by naming the limitation of the built-in type and the bad value it would allow. A list of vague benefits with no example of the wrong data that gets through scores nothing.

Enumerated types: a fixed list of named values

An enumerated type is a non-composite type whose complete set of values is written out in its declaration, in a fixed order. Each value has an ordinal position: the first value is position 0, the second is 1, and so on. It is non-composite because every value of the type is a single item, not a collection of fields.

You define the type once, then declare variables of it. The only values such a variable can be given are the ones in the list, so the compiler or interpreter rejects anything else at the point where the mistake was made rather than at the point where it causes a wrong answer. Because the values have a fixed order, they can be compared with =, <> and <, which is what makes them useful for stages that happen in sequence.

Inside the computer an enumerated value is held as a small integer - its ordinal position - so it takes less memory than a string and compares in a single operation. The names exist for the programmer. That also explains the trap: inserting a new value in the middle of the list shifts every later ordinal, so an ordinal that was written to a file last year no longer means the same stage this year.

An enumerated type is the wrong choice when the list can change while the program runs, when each value needs extra data attached to it, or when the values are numerous and better held in a lookup table. In those cases use a record or class for each value, and hold the set of them in a file or an array.

  • Define the type: TYPE OrderStatus = (Pending, Packed, Dispatched, Delivered, Returned)
  • Declare a variable: DECLARE ThisOrder : OrderStatus
  • Assign a value: ThisOrder <- Packed
  • Compare by position: IF ThisOrder < Dispatched THEN ... treats Pending, Packed and Dispatched in that order.
  • Use in selection: CASE ThisOrder OF Pending : ... / Packed : ... / Delivered : ... ENDCASE
  • Validate input by converting: read a string, then a CASE statement maps 'packed' to Packed and rejects anything else.
  • Positions start at 0: Pending is 0, Packed is 1, Dispatched is 2, Delivered is 3, Returned is 4.
ValueOrdinalMeaning in the order pipeline
Pending0Order received, payment not yet confirmed
Packed1Items picked and boxed, waiting for collection
Dispatched2Left the warehouse, with a carrier
Delivered3Signed for by the customer
Returned4Sent back by the customer and received by the warehouse
Enumerated types are marked on two things: that the list is closed, so no other value can be stored, and that the order is meaningful, so values can be compared. Saying only that it saves typing misses both.

Pointers: variables that hold an address

A pointer is a variable whose value is the address of another variable, of a record, or of a block of memory allocated while the program runs. It does not hold the data itself; it holds the location of the data. Reading or changing the data through the pointer is called dereferencing, and a pointer that holds no address at all is set to NULL.

Pointers are what make dynamic data structures possible. A linked list, a stack or a queue implemented with nodes, or a tree, is built from items that are created as they are needed and joined by storing each item's address in the one before it. The nodes do not have to be next to one another in memory, so the structure can grow and shrink without moving anything, and inserting or deleting an item changes two pointers rather than shifting a whole array.

They are also used to share a large structure without copying it - pass a pointer to a record rather than a copy of the record - and to allow a procedure to change the caller's variable, because the procedure receives the address rather than a copy of the value.

Dereferencing a pointer that has not been given an address, or that still holds the address of memory which has been freed, is a dangling pointer, and it is one of the hardest faults to find because the program may run correctly for months. A memory leak happens when the address of an allocated block is overwritten before the block is released: the memory is still reserved but nothing can reach it. Two pointers to the same location also mean that a change made through one appears through the other, which is easy to overlook when you are debugging.

  • Define the node: TYPE Node / DECLARE Data : STRING / DECLARE Next : POINTER TO Node / ENDTYPE
  • Declare the pointers: DECLARE Head : POINTER TO Node and DECLARE Current : POINTER TO Node
  • Start an empty list: Head <- NULL
  • Follow a link: Current <- Current^.Next - the caret means the value stored at that address.
  • Read the data: OUTPUT Current^.Data
  • Test for the end: WHILE Current <> NULL ... ENDWHILE, because the last node's Next is NULL.
  • Create a node while the program runs and store its address in a pointer, rather than declaring a fixed array of nodes in advance.
A pointer is an address, not a value. If a question asks what is stored in the variable, the answer is the address of the node; the data is reached by dereferencing it.

Sets and records: two composite types with different jobs

A set is an unordered collection of values of the same base type, in which each value can appear at most once. Order is not part of a set, so {Biology, Computing} and {Computing, Biology} are the same set, and adding a value that is already there changes nothing. Sets are used when the question you are asking is membership: which optional subjects this pupil takes, which permissions this user has, which tags this article carries.

The operations you need are adding a value, removing a value, testing membership, and combining sets by union (everything in either), intersection (only what is in both) and difference (what is in the first but not the second). Testing membership is a single operation on a set, whereas on an array it means searching the whole array with a loop, which is why a set is the natural type for a list of options.

A record is the opposite kind of composite: an ordered collection of named fields, each of which may have a different type, treated as one item. The fields are fixed when the type is declared and are reached by name, as in Pupil.Name or Pupil.AverageMark. Records are how real entities are held: a student, an employee, a product, a booking. An array of records gives you a table, and the field that identifies each record is its key field.

Records can be nested, so a record can contain another record or an array, and records are what a file stores: a file of fixed-length records is the usual way of holding a table on disk. The difference from a class is that a record is passive data with no operations of its own, while a class bundles its data with the methods that work on it.

  • Define a set: TYPE Subject = (Art, Biology, Chemistry, Computing, Drama, Economics) then TYPE SubjectSet = SET OF Subject
  • Declare and fill it: DECLARE Choices : SubjectSet then Choices <- [Biology, Computing]
  • Test membership: IF Computing IN Choices THEN ... ENDIF
  • Combine: union gives every subject chosen by either pupil, intersection gives the subjects they share, difference gives the ones chosen by the first but not the second.
  • Define a record: TYPE Pupil / DECLARE Name : STRING / DECLARE YearGroup : INTEGER / DECLARE AverageMark : REAL / ENDTYPE
  • Declare and use it: DECLARE Student : Pupil then Student.Name <- 'Aisha Khan'
  • Make a table: DECLARE Pupils : ARRAY[1:30] OF Pupil, where Name is the key field used to search and sort.
FeatureSetRecord
ContentsValues of one base type onlyFields of different types, fixed when the type is declared
OrderUnordered; {A, B} is the same as {B, A}Ordered as declared, and fields are reached by name
DuplicatesImpossible; a value is either in the set or notAllowed, because two fields may legitimately hold the same value
Main operationMembership test, union, intersection, differenceRead or write a named field, pass the whole record, store it in a file
Typical useOptional subjects, permissions, tags, flagsOne entity such as a pupil, an employee or a booking
A set answers the question is this value in the collection, a record answers the question what are the details of this one thing. Choosing between them is a design mark, and the reason you give has to name that difference.

Classes, objects, and choosing a type for a problem

A class is a template that defines a set of attributes (the data) and a set of methods (the procedures and functions that work on that data). An object is one instance of a class, created while the program runs, with its own copy of the attributes. Creating an object from a class is instantiation, and the method that sets up a new object is its constructor.

The point of a class is encapsulation: the attributes are declared private, so code outside the class cannot change them directly, and the public methods are the only way in. A withdrawal method can check that the amount is positive and that the balance is large enough before it changes anything, which a record cannot do because any code can assign to any field. Encapsulation also hides the implementation: the balance could be stored in pence rather than pounds, and the code that calls GetBalance would not have to change.

A class also allows one class to be built on another and inherit its attributes and methods, so a SavingsAccount can reuse everything an Account already does and add its own interest method. That is the practical reason classes are used for large programs, and it is why a class is the right answer when a question describes an entity that has behaviour as well as data.

Designing a type is a sequence of decisions, not a guess. List the data items the problem mentions, give each one a type, decide which of them belong to one entity, then ask what the program must do with them.

If the values are a closed list, use an enumerated type. If the question is membership of a collection, use a set. If it is one entity with several fields, use a record. If the entity also has operations that must protect its data, use a class. If the number of items is unknown until run time, use pointers to link them.

  • Class name and attributes: CLASS Account / PRIVATE Balance : REAL / PRIVATE Owner : STRING
  • Constructor: PUBLIC PROCEDURE New(OwnerName : STRING, Opening : REAL) sets Owner and Balance.
  • Method that protects the data: PUBLIC PROCEDURE Withdraw(Amount : REAL) checks the amount and the balance before subtracting.
  • Accessor: PUBLIC FUNCTION GetBalance() RETURNS REAL returns Balance without letting the caller change it.
  • Create an object: DECLARE MyAccount : Account then MyAccount.New('J. Patel', 250.00)
  • Call a method: MyAccount.Withdraw(40.00) - the attributes are never touched directly from outside the class.
Situation in the questionType to chooseReason
The month of the year in a sales reportEnumeratedA closed list of twelve named values, and the order is meaningful
The clubs a pupil belongs toSetMembership tests, no duplicates, order irrelevant
One pupil's name, form and average markRecordDifferent types that belong to one entity and must be passed together
A bank account with a balance that can only be changed by a deposit or withdrawalClass and objectData plus the methods that protect it, with many independent instances
A queue of print jobs that grows while the computer is runningPointer to a nodeThe number of items is unknown in advance, so they are linked as they arrive
Design questions are marked on the match between the property that matters and the type you chose, so write the property down. A closed list, membership, one entity, behaviour, or a size that is unknown at run time - name which one your problem has.

File organisation: serial, sequential and random

Two ideas are examined together and must be kept apart. File organisation is the way records are arranged on the storage medium: the order they are written in and where each one sits. File access is the way the program finds a particular record when it wants it. A file can be organised one way and accessed in more than one way, and most lost marks in this section come from answering one question with the other.

A serial file is the simplest: records are written one after another in the order they arrive, with no ordering by any key. A new record is appended at the end. To find a record you must start at the beginning and read forward until you meet it, so on average you read half the file. Serial organisation is ideal for a transaction log or an audit trail, where every event must be kept in the order it happened and nothing is ever searched for by key; writing is fast because the record simply goes at the end.

A sequential file holds its records in order of a key field, such as an account number or a surname. Records are read in that order, and finding one record means reading every record before it, so the cost depends on where it sits. Inserting a record into the middle means rewriting the file, so in practice updates are done in a batch: a sorted transaction file is merged with the master file to produce a new master file. Sequential organisation suits payroll, billing and end-of-term reports, where most records are processed anyway.

A random file (also called a direct file) stores each record at an address calculated from its record key using a hashing algorithm. Records are fixed length so that the address can be computed exactly, and a record can be read or written without touching any other. The price is wasted space, because the file must have more slots than records to leave room for collisions, and the order of records is meaningless, so a sequential report has to be produced some other way.

Access follows from the organisation. Sequential access reads records one after another in the order they are stored and cannot skip any, which is the only way to read a serial file and the normal way to read a sequential file. Direct access jumps straight to a calculated location. A random file always uses direct access, because the hash computes the address.

A sequential file can also be read directly if the records are fixed length, because the position of record n is the header length plus (n - 1) times the record length. What it cannot do is find a record from its key without an index or a calculation.

OrganisationOrder of recordsHow one record is foundAdding a recordBest suited toWeakness
SerialOrder of arrival, no key orderRead from the start until the record is metAppend at the endTransaction logs, audit trails, backups, files passed to another programVery slow to search; cannot jump to a record
SequentialSorted by a key fieldRead from the start comparing keys, unless the record number is knownInsert in key order, which means rewriting the file or merging a transaction fileBatch processing: payroll, billing, statements, reportsSearch time grows with the position of the record; updates are awkward
Random (direct)Position calculated from the key by hashingHash the key, then read the record at that addressHash the key and write at that address, or in the overflow areaInteractive lookup: bank accounts, bookings, stock, customer recordsWasted space, collisions must be handled, no useful order
Choose by how the data is used, not by how big it is. If most records are processed in one run it is batch and sequential suits it; if a user waits at a screen for one record it must be random with direct access, or an indexed file.

Hashing: turning a key into an address

Hashing is the process of applying an algorithm to a record key to produce a record address, so that the record can be stored and found without searching. A good hash function is quick to calculate, always produces the same address for the same key, and spreads the keys evenly over the available addresses so that few of them land in the same place.

The commonest method is division: divide the key by the number of addresses and take the remainder, written key MOD n. Choosing n as a prime number, and not as a number that shares a factor with the keys, spreads the results much better; if every account number ends in 0 and n is 100, every record lands on an address ending in 0 and the rest of the file stays empty.

Folding splits the key into parts, adds the parts and takes the remainder, which uses every digit of the key. Mid-square squares the key and takes digits from the middle of the result. Truncation simply ignores part of the key and uses the rest, which is fast but distributes badly when the ignored part is the part that varies.

Here is the division method used on four account numbers with 97 addresses and a record length of 64 bytes. The record number is the remainder, and the byte address is the record number multiplied by the record length.

To write a record, hash its key, go to that address, and store the record if the slot is empty or already holds that key. To read a record, hash the key, go to the address and compare the key stored there with the key you hashed. If they match, that is the record; if they do not, the record you want was displaced by a collision, so you follow the same collision rule as the program used when it wrote the record. Storing the key inside the record is what makes this check possible, and it is why the key field is never omitted from a hashed file.

A collision happens when two different keys hash to the same address, and it is unavoidable because there are more possible keys than there are addresses. The usual fixes are to probe forward to the next free slot (open addressing), to keep an overflow area at the end of the file with a pointer chain from the collided slot, or to apply a second hash function and try again (rehashing). The more full the file becomes, the more collisions occur, which is why a hashed file is kept well short of full. Deleting a record needs care too: marking the slot empty breaks every later search that probes through it, so the slot is marked as deleted instead.

Record keyKey MOD 97Byte address (x 64)
4127533392
4128543456
2406784992
1199352240
In an answer about hashing, always include the comparison of the stored key after the address is calculated. Without it, the answer describes a calculation that cannot tell a collision from the record it wanted.

Binary floating point: mantissa, exponent and conversion

A fixed-point binary number with a fixed number of bits after the point cannot hold both very large and very small values in a useful range. Floating-point notation solves this the way scientific notation does in denary: the number is stored as a mantissa (the significant digits) multiplied by a power of two given by an exponent. In 9618 both parts are held in two's complement form, and the split between the bits given to each part is stated in the question - a common format is an 8-bit mantissa followed by a 4-bit exponent.

The mantissa is a fraction with the binary point assumed immediately after its sign bit, so a mantissa of m bits holds a value between -1 and just under +1. The exponent is a signed integer that says how far to move the point and in which direction: a positive exponent moves it right, making the number larger, and a negative exponent moves it left, making it smaller. The value is therefore mantissa x 2^exponent, and the same bit pattern means a different number if the split between mantissa and exponent is different.

Binary to denary. Split the pattern at the point given in the question, convert the mantissa as a two's complement fraction, convert the exponent as a two's complement integer, then multiply. For 01011000 0011 the mantissa is 01011000, which is positive and equals 0.1011 in binary, or 0.5 + 0.125 + 0.0625 = 0.6875. The exponent 0011 is +3, so the value is 0.6875 x 2^3 = 0.6875 x 8 = 5.5.

For 10011000 0011 the mantissa begins with 1, so it is negative: invert 10011000 to get 01100111, add 1 to get 01101000, which is 0.8125, so the mantissa is -0.8125. The exponent is +3, so the value is -0.8125 x 8 = -6.5.

Denary to binary reverses the process. Take -6.5: the magnitude 6.5 is 110.1 in binary, which as a fraction is 0.1101 x 2^3, so the exponent is 0011. Write the fraction as eight bits with a 0 sign, 01101000, then negate it by inverting and adding 1, which gives 10011000. The stored pattern is 10011000 0011.

Changing the allocation of bits changes two different things, and exam questions turn on keeping them apart. The mantissa bits set the precision: with m bits there are m - 1 significant binary digits, and values near 1 are spaced 2^-(m-1) apart. The exponent bits set the range: with e bits the exponent runs from -2^(e-1) to 2^(e-1) - 1. Giving bits to the mantissa takes them from the exponent, so the numbers become more precise but the largest and smallest values that can be held get closer together.

Mantissa / exponent bitsSignificant bitsExponent rangeLargest positive valueSmallest positive value
8 / 47-8 to +70.1111111 x 2^7 = 1270.1 x 2^-8 = 0.001953125
12 / 411-8 to +70.11111111111 x 2^7 = 127.93750.1 x 2^-8 = 0.001953125
8 / 87-128 to +127about 1.69 x 10^38about 1.47 x 10^-39
Decode in three separate steps and write each one down: the mantissa as a fraction, the exponent as an integer, then the multiplication. Most lost marks are arithmetic slips in the two's complement conversion, not misunderstanding of the format.

Normalisation, overflow, underflow and rounding error

A floating-point number is normalised when its mantissa has no redundant leading bits: the first bit after the sign bit is significant. For a positive mantissa that means the pattern starts 01; for a negative mantissa it starts 10. To normalise, shift the mantissa left one place at a time and decrease the exponent by 1 for each shift, until the required pattern is reached - which keeps the value the same, because moving the point one place right in the mantissa is balanced by halving the multiplier.

Take 00010110 0100. The mantissa is 0.0010110 = 0.171875 and the exponent is 4, so the value is 2.75. The mantissa starts 00, so shift left twice: 01011000, and the exponent falls from 4 to 2. Check it: 0.1011 x 2^2 = 0.6875 x 4 = 2.75.

For a negative number, 11011000 0101 has mantissa -0.3125 and exponent 5, giving -10.0; it starts 11, so shift left once to 10110000 and drop the exponent to 4, and -0.625 x 2^4 gives -10.0 again.

There are two reasons for normalising. The first is precision: every leading zero (or leading one, for a negative number) occupies a bit that could have held a significant digit, so an unnormalised number stores fewer useful bits than its mantissa allows and loses accuracy. The second is that normalisation makes the representation unique, so each value has exactly one bit pattern; without it, the same number could be stored in several ways, and comparing two floating-point numbers by comparing their patterns would give the wrong answer.

Overflow happens when the exponent needed is larger than the exponent field can hold, so the number is too large to represent; in the 8/4 format anything above 127 or below -128 cannot be stored and the program must raise an error rather than store a wrong value. Underflow happens at the other end: the exponent needed is below the smallest the field can hold, so the number is too small in magnitude to represent and becomes zero. In the 8/4 format the smallest positive normalised value is 0.1 x 2^-8 = 0.001953125, so a calculation that produces 2^-10 underflows to zero.

Rounding error is the third consequence and the most common in practice. Only fractions that are sums of powers of two can be stored exactly, so most denary fractions are approximations. The denary 0.1 is 0.0001100110011... in binary, a recurring fraction, so with an 8-bit mantissa it is held as 01100110 1101, which is 0.099609375 - close, but wrong by about 0.00039. Add it ten thousand times and the error is visible in the answer; test whether two such values are equal and the test can fail although the arithmetic was correct.

Rounding (choosing the nearest representable value) is more accurate than truncation (discarding the extra bits), but neither removes the error, and adding a very small number to a very large one can lose the small number altogether.

  • Normalise: shift the mantissa left, decrease the exponent by 1 each time, stop at 01 for a positive mantissa and 10 for a negative one.
  • Never shift so far that the sign changes: 01111111 shifted left becomes 11111110, a negative number, so that value cannot be normalised further.
  • Largest positive in 8/4 is 0.1111111 x 2^7 = 127; smallest positive normalised is 0.1 x 2^-8 = 0.001953125.
  • Overflow: the exponent needed is too large for the exponent field - raise an error.
  • Underflow: the exponent needed is too small for the exponent field - the value becomes zero.
  • Rounding error: the stored pattern is the nearest representable value, not the exact number, so equality tests and long sums of fractions can be wrong.
ProblemCauseExample in the 8/4 formatEffect
OverflowThe value is larger than the largest the exponent can reach0.1111111 x 2^8 = 254, but the exponent field holds only up to +7The number cannot be stored; the program must report an error or store infinity
UnderflowThe value is smaller than the smallest normalised number2^-4 x 2^-6 = 2^-10, below the limit of 2^-9The value becomes zero, and any later division by it fails
Rounding errorThe exact value is not a sum of powers of two0.1 is stored as 0.099609375Small errors that accumulate over many operations, and unreliable equality tests
Loss of precision through allocationToo few mantissa bits for the accuracy needed1 + 2^-8 cannot be held with a 7-bit mantissa, so it rounds to 1Small differences disappear, and adding a small number to a large one changes nothing
Normalisation is not about making the number smaller. It is about filling the mantissa with significant bits and giving each value one unique pattern, and both of those reasons need to appear in a full-mark answer.

Key terms you must be able to define

User-defined data type
A data type that the programmer declares, built from the built-in types or from other user-defined types, to describe data the language has no suitable type for or to prevent invalid values being stored.
Enumerated type
A non-composite type whose complete set of named values is listed in its declaration, in a fixed order. Each value has an ordinal position, so values can be compared and used in a CASE statement, and no other value can be stored.
Pointer
A variable that holds the address of another variable, record or block of memory rather than the data itself. Pointers are used to build dynamic structures such as linked lists, to share a large structure without copying it, and to let a procedure change the caller's variable.
Set
A composite type holding an unordered collection of values of one base type, with no duplicates. The operations are add, remove and membership test, together with union, intersection and difference.
Record
A composite type holding a fixed set of named fields, which may be of different types, under one identifier. Fields are reached by name, and the field that identifies a record is its key field.
Class and object
A class is a template defining attributes and the methods that operate on them. An object is one instance of a class, created by instantiation, with its own copy of the attributes. A class usually keeps its attributes private and exposes methods, which is encapsulation.
Serial file
A file in which records are stored one after another in the order they arrive, with no ordering by key. A new record is appended at the end and any search must read from the start, so serial organisation suits transaction logs and backups.
Sequential file
A file whose records are stored in order of a key field. Records are read in that order, finding one record means reading all the records before it, and inserting a record in the middle means rewriting the file or merging a sorted transaction file with the master file.
Random file
A file in which each fixed-length record is stored at an address calculated from its record key by a hashing algorithm, so a record can be read or written directly without reading any other. Also called a direct file.
Hashing
Applying an algorithm to a record key to calculate the address at which the record is stored, for example key MOD n with n prime. A good hash function is quick, deterministic and spreads keys evenly; when two keys give the same address the result is a collision.
Mantissa
The part of a floating-point number that holds its significant digits, stored as a two's complement fraction with the binary point after the sign bit. The number of mantissa bits sets the precision.
Exponent
The part of a floating-point number that says how far and in which direction to move the binary point, stored as a two's complement integer. The number of exponent bits sets the range of values that can be held.
Normalisation
Shifting the mantissa left and decreasing the exponent by the same number of places until the mantissa begins 01 for a positive number or 10 for a negative one. It fills the mantissa with significant bits and gives every value a single unique representation.
Overflow and underflow
Overflow is a value too large for the exponent field, so it cannot be represented and an error must be raised. Underflow is a value too small in magnitude for the exponent field, so it becomes zero.

Applied to real systems

9618 sets its questions inside systems you already use - a phone, a school network, a supermarket till, a website, a bank. These show how to apply the theory to a context you have not seen before, then you write your own.

An enumerated type in an order-tracking system

An online shop stores the stage an order has reached as an enumerated type: TYPE OrderStatus = (Pending, Packed, Dispatched, Delivered, Returned). The warehouse system can only assign one of those five values, so a mistyped status cannot enter the database, and the customer's tracking page uses a CASE statement over the same type. Because the values are ordered, the system can also ask whether an order has passed Dispatched by comparing ordinals, which is impossible if the status is stored as free text. It shows what an enumerated type buys you: a closed list of values that the program enforces.

A class and objects in a game engine

A game defines a class Player with private attributes for position, health and score, and public methods such as Move, TakeDamage and AddScore. Each player in a match is a separate object created from the class, with its own copy of those attributes, so four players share one piece of code but four sets of data. Health can only be reduced through TakeDamage, which checks that it does not fall below zero, so no other part of the program can put a player into an impossible state. It shows encapsulation and instantiation in one example, and why a record would not be enough when the data has rules attached.

A bank's account file: hashing and direct access

A bank stores customer accounts in a random file with fixed-length records, and the account number is hashed to give the record address. When a card is used at a cash machine the terminal sends the account number, the bank hashes it, reads that one record and checks the balance, without reading any of the other millions of records. Two account numbers can hash to the same address, so the file keeps an overflow area and the stored key is compared before the record is accepted. It shows why interactive systems need direct access and what a collision costs.

A monthly payroll run: sequential organisation and batch processing

A payroll system keeps a master file sorted by employee number, and each month a transaction file of changes - new starters, leavers, overtime, deductions - is sorted by the same key. The two files are then read in parallel and merged to produce a new master file and the payslips, with every record processed once and in order. Nothing needs to be looked up individually, so sequential organisation with sequential access is efficient and the sorted output is a useful by-product. It shows the case where reading the whole file is the point, which is exactly when sequential organisation beats a hashed file.

Why 0.1 is not exact: IEEE 754 single precision

The IEEE 754 single-precision format is a real floating-point standard that allocates 1 sign bit, 8 exponent bits and 23 mantissa bits, and it has the same problem as the 8/4 format used in exams. The denary value 0.1 is 0.0001100110011... in binary, a recurring fraction, so it is stored as the nearest value the 23 mantissa bits allow rather than exactly 0.1. Adding such values repeatedly produces answers that differ from the expected total in the last digits, which is why programs compare floating-point numbers with a tolerance and why money is often held in integer pence. It shows that changing the allocation of bits changes the precision but never removes the approximation.

Flashcards

Click a card to flip it. Mark honestly — the tracker uses this.

Q1Why are user-defined data types necessary?
Because built-in types cannot describe the data or cannot stop it being used wrongly. A closed list of values (an order status) is not enforced by INTEGER or STRING; a group of different fields (a pupil) needs one identifier so it can be passed and stored as a unit; and a structure whose size is unknown until run time needs pointers. They also make programs readable, maintainable and reusable, and a class can protect its data by keeping attributes private.
Q2Define an enumerated type and give one advantage it has over storing the same data as a string.
A non-composite type whose complete set of named values is declared in a fixed order, each with an ordinal position, for example TYPE OrderStatus = (Pending, Packed, Dispatched, Delivered, Returned). A string accepts any text, so 'Dispatchedd' or 'sent' would be stored without complaint and later comparisons would fail; the enumerated type rejects every value outside the list, compares in one operation, and takes less memory.
Q3What is a pointer, and what is it used for?
A variable that holds the address of another variable, record or block of memory rather than the data itself, and is dereferenced to reach that data; a pointer holding no address is NULL. Pointers build dynamic structures such as linked lists, stacks, queues and trees, whose items are created at run time and linked rather than stored in a fixed array; they also share a large structure without copying it and let a procedure change the caller's variable.
Q4Define a set and a record, and name the operations of a set.
A set is an unordered collection of values of one base type with no duplicates; its operations are add, remove, membership test (IN), union, intersection and difference. A record is a fixed set of named fields, possibly of different types, held under one identifier and reached by field name, with one field acting as the key field. Sets answer membership questions; records describe one entity.
Q5What is a class, what is an object, and what is encapsulation?
A class is a template defining attributes and the methods that work on them. An object is one instance of a class, created by instantiation, with its own copy of the attributes. Encapsulation is keeping the attributes private and allowing changes only through public methods, so the data cannot be put into an invalid state and the implementation can change without affecting the code that uses the class.
Q6How do you choose a user-defined data type for a problem?
List the data items and type each one, decide which belong to one entity, then match the property that matters: a closed list of values is an enumerated type; membership of a collection is a set; one entity with several fields is a record; data that must be protected by operations is a class; a number of items unknown until run time needs pointers to linked nodes.
Q7Describe serial, sequential and random file organisation.
Serial: records appended in the order they arrive, no key order, new records at the end, searching means reading from the start - used for transaction logs and backups. Sequential: records sorted by a key field, read in that order, inserting means rewriting the file or merging a transaction file - used for batch work such as payroll. Random (direct): fixed-length records at addresses calculated from the key by hashing, read directly - used for interactive lookup such as bank accounts.
Q8Which file organisations use sequential access and which use direct access?
Sequential access, reading records one after another in stored order, is the only way to read a serial file and the normal way to read a sequential file. Direct access, jumping to a calculated address, is used by random files because the hash computes the address, and is also possible on a sequential file when the records are fixed length and the record number is known, since the address is the header length plus (n - 1) times the record length.
Q9Describe hashing and say what happens on a collision.
Hashing applies an algorithm to a record key to calculate the record address, for example key MOD n with n chosen prime so the keys spread evenly; folding, mid-square and truncation are alternatives. To read a record the key is hashed, the record at that address is read and its stored key compared. A collision is two keys hashing to one address, handled by open addressing (probing the next free slot), an overflow area with a pointer chain, or rehashing with a second function.
Q10How is a binary floating-point number stored, and what does each part control?
As a two's complement mantissa with the binary point after its sign bit, multiplied by 2 raised to a two's complement exponent: value = mantissa x 2^exponent. The mantissa bits set the precision, m bits giving m - 1 significant binary digits, and the exponent bits set the range, e bits giving -2^(e-1) to 2^(e-1) - 1. For 01011000 0011 the mantissa is 0.6875 and the exponent is 3, so the value is 5.5.
Q11What is normalisation, how is it done, and why is it needed?
A mantissa is normalised when it begins 01 (positive) or 10 (negative), so the first bit after the sign is significant. Shift the mantissa left one place at a time and decrease the exponent by 1 for each shift, for example 00010110 0100 becomes 01011000 0010, both equal to 2.75. It is needed to fill the mantissa with significant bits, which maximises precision, and to give every value one unique representation, so patterns can be compared reliably.
Q12How do overflow, underflow and rounding error arise in floating point?
Overflow: the value needs an exponent larger than the field allows, so it cannot be represented and an error is raised. Underflow: the value is too small in magnitude for the exponent field, so it becomes zero. Rounding error: most denary fractions are not sums of powers of two, so the stored value is the nearest representable one - 0.1 becomes 0.099609375 with an 8-bit mantissa - and the small errors accumulate over repeated operations.
0 of 12 marked as known

Exam-style questions

8 questions, 28 marks. Write your answer, then reveal the mark scheme and tick each point you actually made. Answering honestly here is what makes the rest of the site useful.

StateAO11 mark
State the name of the non-composite data type that consists of a list of named values in a fixed order.

Mark scheme — tick each point you actually made

Examiner tip: One mark, one term. Do not write 'user-defined type' - that is the category, not this type, and it does not identify the fixed ordered list.
DefineAO12 marks
Define the terms pointer and record.

Mark scheme — tick each point you actually made

Examiner tip: The pointer mark is for the word address. 'A pointer points to something' restates the name and scores nothing.
ExplainAO23 marks
A programmer stores the day of the week in a pupil's timetable as a string. Explain why an enumerated type would be a better choice.

Mark scheme — tick each point you actually made

Examiner tip: Name the bad value the string would allow. 'It is easier to read' on its own is a weak point and will not earn the first mark.
ExplainAO23 marks
Explain why floating-point numbers are usually stored in normalised form.

Mark scheme — tick each point you actually made

Examiner tip: Give both reasons - precision and a unique representation - and say what goes wrong without it. Two sentences on precision alone caps the answer at two marks.
CalculateAO24 marks
A computer stores real numbers in 12 bits: an 8-bit mantissa followed by a 4-bit exponent, both in two's complement form. (a) Convert 01011000 0011 to denary. (b) Convert -6.5 to this format. Show your working.

Mark scheme — tick each point you actually made

Examiner tip: Write the mantissa value and the exponent separately before multiplying, and show the invert-and-add-one step for the negative number. A bare bit pattern with no working earns nothing when the pattern is wrong.
ExplainAO24 marks
Explain how a hashing algorithm is used to write a record to and read a record from a random file, including how a collision is dealt with.

Mark scheme — tick each point you actually made

Examiner tip: The comparison of the stored key is the mark most often missed. Without it, two keys that collide cannot be told apart and the answer does not describe retrieval.
CompareAO25 marks
A company holds a customer file of 200 000 records. Call-centre staff look up one customer at a time while the customer is on the telephone, and the accounts department produces a statement for every customer once a month. Compare sequential and random file organisation for this file, and justify which organisation should be used.

Mark scheme — tick each point you actually made

Examiner tip: Compare means both directions: say what each organisation is good at and what it costs in this company's two tasks. A description of both types with no judgement on which to use cannot reach full marks.
DiscussAO36 marks
A computer stores real numbers in a fixed number of bits as a two's complement mantissa and exponent. Discuss the consequences of this representation, including the effect of changing the allocation of bits between the mantissa and the exponent, overflow, underflow and rounding error.

Mark scheme — tick each point you actually made

Examiner tip: Take the four named issues in turn, then finish with what a programmer should do about them. Marks are for the mechanism in each case, so say why the exponent runs out, not just that it does.

Exam technique for this topic

  • Separate organisation from access in your first sentence. Organisation is how the records are arranged on the medium; access is how the program finds one. A question about searching a file is asking about access, even when it starts with the word file.
  • Answer type-design questions by naming the property that decides the choice: a closed list of values, membership of a collection, one entity with several fields, data with behaviour, or a size that is unknown until run time. The name of the type on its own is worth half the marks.
  • In every floating-point conversion, write the three lines: mantissa as a two's complement fraction, exponent as a signed integer, then the multiplication by 2^exponent. Check the sign of the mantissa first, because a negative mantissa is where most arithmetic errors happen.
  • When you normalise, change the exponent by the number of places you shifted, in the opposite direction: shifting the mantissa left decreases the exponent. Then test your answer by converting both patterns back to denary - they must be equal.
  • For a hashing answer, give the algorithm, the address calculation including the record length, and the comparison of the stored key. Finish with what happens on a collision, because that is the part that separates a description of hashing from a description of storing and retrieving a record.

Why students lose marks here

A student wroteA student wrote: 'A set is a list of values in a fixed order, and it can hold the same value twice if the user enters it twice.'
Why it loses marksBoth halves are wrong. A set is unordered, so {Biology, Computing} and {Computing, Biology} are the same set, and a value either belongs to a set or does not, so duplicates are impossible. A fixed order with duplicates allowed describes an array or a list, not a set.
The answer that scoresA set is a composite data type holding an unordered collection of values of the same base type, in which each value can appear at most once. Because order carries no meaning, two sets with the same members are equal however they were built, and adding a value that is already present changes nothing. The operations are adding and removing a value, testing membership with IN, and combining sets by union, intersection and difference. A set is the right type when the question is membership, such as which optional subjects a pupil takes, and an array is the right type when order and repetition matter.
A student wroteA student wrote: 'A random file is one where the records are stored in a random order, so you have to search the whole file to find a record.'
Why it loses marksRandom does not mean shuffled, and it certainly does not mean slow. The records are placed at addresses calculated from their keys by a hashing algorithm, which is precisely what allows a record to be found without searching. Describing the file as shuffled and then saying it must be searched contradicts the whole purpose of the organisation.
The answer that scoresA random file, also called a direct file, stores each fixed-length record at an address calculated from its record key by a hashing algorithm. To find a record the program hashes the key and goes straight to that address, so one disk read is enough however large the file is; there is no searching. The order of the records is meaningless, which is why a sequential report from such a file needs a separate sorted index, and the cost of the method is wasted space and the need to handle collisions, where two keys hash to the same address.
A student wroteA student wrote: 'Giving more bits to the exponent makes the numbers more precise, because the exponent makes the number bigger.'
Why it loses marksThe exponent sets the range, not the precision. Precision is the number of significant binary digits, which is decided by the mantissa. Taking a bit from the mantissa to give to the exponent widens the range but coarsens the gaps between representable values.
The answer that scoresThe mantissa holds the significant digits, so m mantissa bits give m - 1 significant binary digits and set the precision: with an 8-bit mantissa the gap near 1 is 2^-7, and with a 12-bit mantissa it is 2^-11. The exponent sets the range: e exponent bits give a two's complement exponent from -2^(e-1) to 2^(e-1) - 1, so 4 bits give -8 to +7 and 8 bits give -128 to +127. Moving a bit from the mantissa to the exponent therefore widens the range of values that can be held but makes the values coarser, so the two cannot be increased together in a fixed word length.
A student wroteA student wrote: 'To normalise 00010110 0100 I shifted the mantissa left two places to 01011000, so the answer is 01011000 0100.'
Why it loses marksThe mantissa was shifted but the exponent was left alone, which multiplies the value by four. Shifting the mantissa left moves the binary point right, so the exponent must decrease by one for every place shifted to keep the value the same.
The answer that scoresThe mantissa 00010110 is 0.0010110, or 0.171875, and the exponent 0100 is 4, so the value is 0.171875 x 16 = 2.75. The mantissa begins 00, so shift it left twice to 01011000 and decrease the exponent by two, from 4 to 2. The normalised pattern is 01011000 0010, and the check is 0.1011 x 2^2 = 0.6875 x 4 = 2.75, which is the original value. The rule is that a normalised positive mantissa begins 01 and a normalised negative mantissa begins 10.
A student wroteA student wrote: '0.1 is stored exactly in binary floating point, so 0.1 + 0.2 gives exactly 0.3.'
Why it loses marksOnly fractions that are sums of powers of two can be stored exactly. 0.1 in binary is the recurring fraction 0.0001100110011..., so any fixed number of mantissa bits stores an approximation, and the error survives the addition.
The answer that scoresThe denary fraction 0.1 is 0.0001100110011... in binary, which recurs, so it cannot be represented exactly in a finite number of bits. With an 8-bit mantissa it is normalised to 0.1100110 x 2^-3 and stored as 01100110 1101, which is 0.099609375 - an error of about 0.00039. That is a rounding error, and it accumulates when such values are added repeatedly or multiplied, so a program that tests two calculated values for equality may get the wrong answer. The consequences are handled by comparing with a tolerance, by holding money in integer pence, and by allowing enough mantissa bits for the accuracy the problem needs.