Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
9677793
Add maths prelims and intro to data/function reps
Nov 6, 2026
3aa1375
Add a course outline to week 1
Nov 6, 2026
6b8075e
Remove unused cmake notes, redistribute optimisation and FP
Nov 6, 2026
4766fcc
Update container text to make simpler and more general
Nov 6, 2026
dbbf94a
Moving error handling to week 5
Nov 6, 2026
da630ac
Move datastructures to wk 10 because of dependencies
Nov 6, 2026
a72db8d
Move optimisation advice to code quality wwek
Nov 6, 2026
154a446
rename week 1 folder
Nov 6, 2026
e883867
Moving timing out of libraries
Nov 9, 2026
8a17105
Update 00introCS/basicTypes.md
mmcleod89 Nov 10, 2026
13bf49e
Update 05libraries/sec03CppCodeDesign.md
mmcleod89 Nov 10, 2026
dfb35c4
Update 00introCS/basicTypes.md
mmcleod89 Nov 10, 2026
8d215ac
Update 00introCS/dataRepresentation.md
mmcleod89 Nov 10, 2026
ecfd151
Update 00introCS/dataRepresentation.md
mmcleod89 Nov 10, 2026
e569a70
Update 05libraries/sec03CppCodeDesign.md
mmcleod89 Nov 10, 2026
0b21614
Update 00introCS/dataRepresentation.md
mmcleod89 Nov 10, 2026
d78017a
Arithmetic typo, adding powerset notation
Nov 10, 2026
9bdd471
Merge branch 'mm_updates2026' of github.com:UCL/research-computing-wi…
Nov 10, 2026
ffa8b69
Remove redundant key check
Nov 10, 2026
74b53ee
Replace analytic forms with algorithms for computable functions
Nov 10, 2026
c3883c3
Update 00introCS/functionRepresentation.md
mmcleod89 Nov 10, 2026
e7c5f3c
Update 00introCS/functionRepresentation.md
mmcleod89 Nov 10, 2026
a668c1e
Update 00introCS/functionRepresentation.md
mmcleod89 Nov 10, 2026
3f8f0d3
Boolean typo
Nov 10, 2026
beb4fc4
Update 00introCS/functionRepresentation.md
mmcleod89 Nov 10, 2026
1d71c95
Update 00introCS/index.md
mmcleod89 Nov 10, 2026
b64ea6e
Add basic types to index
Nov 10, 2026
534a9e6
Merge branch 'mm_updates2026' of github.com:UCL/research-computing-wi…
Nov 10, 2026
905f138
Topology typo!
Nov 10, 2026
c5fc4bf
Apply batched suggestions from code review
mmcleod89 Nov 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions 00introCS/basicTypes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
title: Basic Types
---

# Basic Types in Programming Languages

In order to effectively work in any programming language, we need a basic understanding of a number of fundamental types. We will give these types by the names in C++, although there are generally analogues in other languages.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should add here something like:

The size and range of some C++ types depend on the implementation. We describe common choices on modern desktop and server systems below, and mark these as "typically". Use sizeof(T) to inspect a type's storage size in bytes and std::numeric_limits<T> from <limits> to inspect its numerical properties.

?

## Boolean Types

Boolean types represent true/false values. In C++ the type is called `bool` and it takes 1 byte of memory.

## Integer Types

All programming languages will have some types to describe integer values. We use these to represent things that in mathematics are in the set of _natural numbers_ $\mathbb{N}$ or _integers_ $\mathbb{Z}$. In C++ we might most commonly use:

- `int`: integers that can be positive or negative, and take 4 bytes of memory.
- `unsigned int`: integers that can only be positive, and take 4 bytes of memory. Because they don't have to represent negative numbers as well, `unsigned int` can represent larger values than `int`.
- `std::size_t`: unsigned integers that can represent that size of the largest possible object; this is usually determined by the addressing on an architecture. For example, on a 64-bit system `size_t` will typically be 8 bytes. This is the standard type to use for things like indexing arrays/vectors since it can't be negative and is guaranteed to be able to represent the largest index of an element of an array.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest to add something like

Care is needed with subtraction and comparisons involving signed integers. When you only need to visit each element of a container, prefer a range-based for loop.


There are many other types that C++ offers, although not all of them are standard. You should use the C++ documentation to check whether a type that you want to use is standardised and portable across different architectures. (As an example, I used to use `uint` for unsigned integers until some Mac users informed me that it doesn't compile with the `clang` compiler!)

## Strings and Characters

A single character can be represented using `char`, which takes a single byte. For strings you are advised to use the C++ type `std::string`. In some places you will come across `char*`, an C-style array of `char`, as an alternative to C++ strings. These should only be used where necessary for compatibility. The most common place for this to come up is command line arguments for the `main` function.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
A single character can be represented using `char`, which takes a single byte. For strings you are advised to use the C++ type `std::string`. In some places you will come across `char*`, an C-style array of `char`, as an alternative to C++ strings. These should only be used where necessary for compatibility. The most common place for this to come up is command line arguments for the `main` function.
A single character can be represented using `char`, which takes a single byte. For strings you are advised to use the C++ type `std::string`. In some places you will come across `char*`, a C-style array of `char`, as an alternative to C++ strings. These should only be used where necessary for compatibility. The most common place for this to come up is command line arguments for the `main` function.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, nerd comment: char* is technically a pointer to char, not really an array. Perhaps worth reminding them that the pointer itself carries no length information and could point at rubbish...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did originally write up a bunch of stuff about pointers but I was worried that this is the prelim material and they don't know what a pointer is yet. I only really included char* because I was worried they would see it in command line arguments. Perhaps it's better to defer char* to later to avoid confusing them? Most of the time they should be using std::string anyway nowadays.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair - I suppose we would anyway cover it on the first day via a little Hello world program, so maybe we don't even need to cover it in the prereqs then.


## Floating Point Representation of Real Numbers

The real numbers, $\mathbb{R}$, is the set of numbers along the number line. There are infinitely many real numbers in any finite interval, and this poses a problem for computation: we clearly cannot represent all the real numbers exactly, since we have a finite amount of space in which to represent them. Floating point representation is how we typically deal with approximating the real numbers in code. Floating point numbers can in principle have any level of precision, but the most common are:

- `float`: 32 bits (4 bytes),
- `double`: 64 bits (8 bytes),
- `long double`: Usually 80 or 128 bits depending on processor,
- `half`: 16 bits, not part of the C/C++ standard but often used on hardware like GPUs.

The data for floating point numbers are split into two parts, the mantissa and the exponent. It is comparable to scientific notation (when you write a number as $a \times 10^b$) except that it uses powers of 2 instead of 10. We can write a floating point number $n$ as

$n = m \times 2^e$

where $m$ is the mantissa and $e$ is the exponent. The mantissa is usually represented such that there is a radix point after the first significant (non zero) digit. In other words, the binary `001011` would represent the mantissa `1.011` (again, in base 2). The exponent is a signed integer.

The size of the datatype affects both the precision of the mantissa and the range of values for the exponent (and therefore how large and small the values represented can be).

- `float` has around 7 significant figures
- `double` has around 16 significant figures

Floating point computation **is not exact**.

- Adding values of very different sizes leads to significant loss of precision since values must be converted to have the same exponent to be added together. This means the difference in scale is pushed into the mantissa, which then loses precision due to leading `0` digits on the smaller number. In some cases the smaller number may be so small that the closest representable number with that exponent is `0` and so the addition is lost completely.
- Subtracting values which are close in size leads to cancellation of many digits and a result with far fewer significant digits and therefore lower precision.
- Identities from real arithmetic do not necessarily hold, in particular **addition and multiplication are not associative**, so $(a + b) + c \neq a + (b + c)$ in floating point!
- Handling these difficulties in numerical methods is a major field in and of itself. Many numerical algorithms are specially crafted to correct rounding errors in floating point arithmetic.

### Floating Point Precision

Higher precision floating point numbers (like `double` as opposed to `float`) will give more accurate results when doing numerical work, but may also be slower to perform operations. Historically `double` operations have taken more time to compute than `float` operations, although this is no longer typically the case on modern CPUs. Nevertheless, if you are exploiting SIMD registers for maximal performance, fewer `double` values can fit in an available register and therefore fewer operations can be performed in a given amount of time. Some fast algorithms use single precision `float` or even half precision floating point numbers in areas where the results will not be significantly impacted by this. This is particularly common in areas like statistics and machine learning where the statistical variance is much larger than the precision of the floating point numbers. You must always bear in mind that the use of lower precision floating point types can lead to numerical instability from cancellation errors or division/multiplication by extreme values causing under or overflow. You should always have tests for robustness and precision to check that any compromises made to precision are acceptable.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Higher precision floating point numbers (like `double` as opposed to `float`) will give more accurate results when doing numerical work, but may also be slower to perform operations. Historically `double` operations have taken more time to compute than `float` operations, although this is no longer typically the case on modern CPUs. Nevertheless, if you are exploiting SIMD registers for maximal performance, fewer `double` values can fit in an available register and therefore fewer operations can be performed in a given amount of time. Some fast algorithms use single precision `float` or even half precision floating point numbers in areas where the results will not be significantly impacted by this. This is particularly common in areas like statistics and machine learning where the statistical variance is much larger than the precision of the floating point numbers. You must always bear in mind that the use of lower precision floating point types can lead to numerical instability from cancellation errors or division/multiplication by extreme values causing under or overflow. You should always have tests for robustness and precision to check that any compromises made to precision are acceptable.
Higher precision floating point numbers (like `double` as opposed to `float`) can reduce rounding error when doing numerical work, but may also be slower to perform operations. Historically `double` operations have taken more time to compute than `float` operations, although this is no longer typically the case on modern CPUs. Nevertheless, if you are exploiting SIMD registers for maximal performance, fewer `double` values can fit in an available register and therefore fewer operations can be performed in a given amount of time. Some fast algorithms use single precision `float` or even half precision floating point numbers in areas where the results will not be significantly impacted by this. This is particularly common in areas like statistics and machine learning where the statistical variance is much larger than the precision of the floating point numbers. You must always bear in mind that the use of lower precision floating point types can lead to numerical instability from cancellation errors or division/multiplication by extreme values causing under or overflow. You should always have tests for robustness and precision to check that any compromises made to precision are acceptable.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest to add something like the following for perspective:

Precision also affects how much memory a calculation needs. For example, storing an array using 32-bit rather than 64-bit numbers halves the memory needed for its elements. This can allow larger problems to fit into a GPU's limited memory and reduce the amount of data that must be moved during a calculation, potentially improving performance. The rapid growth of AI has accelerated interest in lower-precision hardware and algorithms, including their use in scientific computing. Many algorithms use mixed precision: lower precision for much of the work, with higher precision retained for sensitive steps. The aim is to reduce computing costs while still meeting the accuracy requirements of the application; this must be checked for the problem being solved.


## Containers

Containers are important to most programming languages; almost all programmers will need to work on things like lists of data, or vectors and matrices. Some commonly used containers, that we will use repeatedly throughout this course, are:

- `std::array<T, N>`: a type for storing exactly `N` elements of type `T`, with `N` known at compile time.
- `std::vector<T>`: a type for storing arbitrary numbers of elements of type `T`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- `std::vector<T>`: a type for storing arbitrary numbers of elements of type `T`.
- `std::vector<T>`: a type for storing a resizable sequence of elements of type `T`, subject to memory and implementation limits.

- `std::map<A, B>`: a type for storing key-value pairs where keys are unique and have type `A` and values have type `B`.

99 changes: 99 additions & 0 deletions 00introCS/dataRepresentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
---
title: Data Representation
---

# Background: Basic Data Representation

In order to write effective scientific programs, you will need to be able to translate real world and mathematical concepts into some kind of data representation. This is arguably the most fundamental process to understand in computing, since nothing can be worked with that cannot be represented. You may find that standard representations for many common objects (like vectors, complex numbers etc.) already exist, but it is absolutely vital that you understand how to construct these yourself. In this section, we will look at some examples of how to translate concepts into data representations in a language agnostic way.

## What is a data representation?

In order to represent something on a computer we need to have a properly formalised idea of exactly what information needs to be stored and how. Computers themselves have only sequences of zeros and ones to work with, and the computer itself doesn't know what it is you're trying to represent with it and can't make any decisions on its own about the correct thing to do with them.

Later in this course we will look at the object-oriented approach of combining data and functionality together, but for now we will restrict ourselves to the more fundamental topic of data alone.

## Defining structures

Programming languages usually provide us with a number of basic types of data that we can utilise, including:

- integers
- real numbers (approximated as "floating point")
- characters and strings
- arrays / lists

Almost all languages, including C++ and Python, will give us ways of bundling data together into a larger structure that a collection of named _fields_ of various types. In C++ and Python these are called _classes_[^struct-class]. Deciding what fields are needed for a class is a a fundamental skill in translating your ideas into computable data.

In C++, we must choose explicitly both the name and the _type_ of the data being held: is it an integer, or string, a floating point number? This is because C++ is _statically typed_ (variables must have a well known type that cannot change over the course of the program) whereas a language like Python is _dynamically typed_ (variables have the type of whatever data is assigned to them, and their type can change when their data is updated).

[^struct-class]: Some of you may be familiar with _structs_ from C; the `struct` keyword from C is also valid in C++ and is also used to define a class. When declaring a data type with `struct` the members are public by default, whereas when using `class` the members are private by default; this is the only distinction made in C++ between these two keywords.

## Example 1a: a circle

Suppose our model requires us to represent a circle in a 2D space. This could be because we need to draw it for a graphical output, or we need to keep track of everything that happens within a given radius of some object, or perhaps we need to do collision detections for a physical simulation. What information do we need to store in order to represent a circle on a computer?

In order to answer this question, we should start by thinking about the least information necessary to fully define a circle: it will need a position $(x,y)$ and a radius, $r$. Everything else about a circle can be derived from this information: the circumference, the area, whether something is inside or outside of the circle, and so on. In a purely mathematical sense, $x$, $y$, and $r$ are all _real numbers_; on a computer our best way to represent real numbers is _floating point_ numbers, i.e. `float` or `double`. A class to represent a circle could look like this in C++:

```cpp
class Circle
{
public:
double centre_x;
double centre_y;
double radius;
};
```

or like this in Python:

```python
class Circle:
centre_x: float
centre_y: float
radius: float
```

Note that in Python we have included the optional type annotations for clarity, and that `float` in Python has double precision and is therefore equivalent to `double` in C++.

## Example 1b: adding colour to a circle

Consider now the graphical application of our circle class: we don't just need to know where it is and how big it is any more, but also what colour to render it. A colour on the computer is usually represented as a triple $(r, g, b)$ of _red_, _green_, and _blue_ intensities from 0 to 255. We can write a simple class to represent a colour then in the following way:

```cpp
class Colour
{
public:
int r;
int g;
int b;
};
```

If we've defined a `Colour` class, we can add it to our `Circle` class:

```cpp
class Circle
{
public:
double centre_x;
double centre_y;
double radius;
Colour colour;
};
```

This kind of nested data structure is very helpful in building up complex structures, and more more readable than giving the `Circle` class its own separate red, green, and blue member variables.

The Python equivalent would be:

```python
class Colour:
r: int
g: int
b: int

class Circle:
centre_x: float
centre_y: float
radius: float
colour: Colour
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest to add a final remark along the lines of

A useful data representation stores the information needed for the task. A centre and radius are enough to describe the geometry of a circle, but a drawing program may also need its colour, and a simulation of a moving circular object may need its velocity and mass. We therefore choose a representation by considering both the object and what our program needs to do with it.

Choosing fields and types is only part of defining a representation. We also need to specify what the values mean: for example, the coordinate system and units used for the centre and radius. We should state which values are valid, such as requiring a non-negative radius. These rules need to be documented and, where appropriate, checked by the program.

Loading
Loading