TutorChase logo
Login
AQA A-Level Computer Science

1.1.6 Random number generation and exception handling

Random number generation and exception handling are key programming tools that help add unpredictability and manage errors. This topic supports reliability and robustness in software.

Random number generation

Random number generation allows computer programs to produce numbers that appear unpredictable. These are widely used in areas such as gaming, simulations, encryption, testing, and procedural content generation. Although most computers are deterministic machines (they follow precise instructions), random number functions use special algorithms to simulate randomness, often called pseudorandom number generation.

What is a pseudorandom number?

A pseudorandom number is one that appears random but is actually generated by a deterministic algorithm. It is not truly random but follows a pattern that is difficult to predict without knowing the algorithm and initial state (called the seed).

Built-in random number generators

Most high-level programming languages provide built-in libraries or functions that allow programmers to generate random numbers easily. These functions often support generation of:

  • Random integers: whole numbers within a specified range

  • Random floating-point numbers (floats): decimal values within a specified range

Generating random integers

To produce a random integer within a range (for example, from 1 to 10), the following syntax is commonly used:

Python:

 import random
random.randint(1, 10)  # Includes both 1 and 10

Java:

import java.util.Random;
Random rand = new Random();
int result = rand.nextInt(10) + 1;  // Produces 1 to 10 inclusive

C#:

Random rand = new Random();
int result = rand.Next(1, 11);  // Upper bound is exclusive

These built-in functions allow for concise generation of integers in any valid range. They are often used in games (e.g. to roll dice), quizzes (to select random questions), or simulations (to model randomness).

Generating random floats

Random floating-point numbers are typically generated within a range such as 0.0 ≤ x < 1.0. These are often scaled to a specific range by using mathematical operations.

Python:

random.random()  # Returns float between 0.0 and 1.0
random.uniform(5.0, 10.0)  # Returns float between 5.0 and 10.0

Java:

 double result = rand.nextDouble();  // Between 0.0 and 1.0

C#:

double result = rand.NextDouble();  // Between 0.0 and 1.0

If you want a float between 2.5 and 7.5, you would calculate:

result = lowerBound + (upperBound - lowerBound) * randomFloat

For example:

result = 2.5 + (7.5 - 2.5) * random()

This scaling technique allows you to produce random decimal values within any chosen range.

Practical uses of random numbers

Random number generation has many practical applications:

  • Games: Randomise enemy behaviour, dice rolls, card draws, etc.

  • Simulations: Model real-life randomness, such as weather or traffic.

  • Testing: Generate varied data to test edge cases or performance.

  • Security: Create random passwords, tokens, and encryption keys.

  • Machine learning: Randomly split data for training and testing.

Using random numbers helps to simulate unpredictability and makes many systems more realistic and engaging.

Seeding

A seed is a starting value that initialises the random number generator. Seeding ensures that the sequence of random numbers generated is repeatable. Without a seed, the generator typically uses the current time or system entropy, which leads to different results every time the program is run.

Why use a seed?

  • Reproducibility: Useful in testing or debugging to produce consistent results.

  • Determinism: Important in simulations where results must be repeated.

  • Control: Makes the output predictable during development.

Examples of setting a seed

Python:

 random.seed(123)

Java:

 Random rand = new Random(123);

C#:

 Random rand = new Random(123);

In all cases, the same seed will produce the same sequence of random numbers. This is especially useful when testing algorithms where you want consistency across multiple runs.

Exception handling

Exception handling is the process of detecting and responding to errors that occur while a program is running. Instead of crashing the program, proper exception handling lets the program continue gracefully or provide a meaningful error message to the user.

What is an exception?

An exception is an unexpected event that disrupts the flow of a program. Examples of common exceptions include:

  • Divide by zero: Attempting to divide a number by zero

  • Invalid input: Trying to convert text to a number when the text is not numeric

  • File not found: Trying to open a file that doesn’t exist

  • Index out of range: Accessing an element outside the bounds of a list or array

Without exception handling, these errors would cause the program to stop abruptly.

Structure of exception handling

Most programming languages use a structure involving keywords like try, except, catch, and finally.

Python syntax

try:
    # Code that might cause an error
    num = int(input("Enter a number: "))
    result = 10 / num
except ValueError:
    print("Input must be a number.")
except ZeroDivisionError:
    print("You cannot divide by zero.")
finally:
    print("Program has finished.")
  • try: Contains code that might raise an exception.

  • except: Catches and handles specific exceptions.

  • finally: Always runs at the end, regardless of whether an error occurred.

Java syntax

try {
    int num = scanner.nextInt();
    int result = 10 / num;
} catch (ArithmeticException e) {
    System.out.println("Division by zero.");
} catch (InputMismatchException e) {
    System.out.println("Invalid input.");
} finally {
    System.out.println("End of program.");
}

C# syntax

try {
    int num = int.Parse(Console.ReadLine());
    int result = 10 / num;
} catch (DivideByZeroException) {
    Console.WriteLine("Cannot divide by zero.");
} catch (FormatException) {
    Console.WriteLine("Invalid number format.");
} finally {
    Console.WriteLine("Done.");
}

Common types of exceptions

Here are some of the most commonly encountered exceptions:

  • ZeroDivisionError (Python), ArithmeticException (Java): Raised when dividing by zero.

  • ValueError (Python), InputMismatchException (Java): Occurs when the input does not match the expected type.

  • FileNotFoundError (Python): Happens when a file cannot be located.

  • IndexError (Python), ArrayIndexOutOfBoundsException (Java): Triggered when accessing an index outside the bounds of a list or array.

  • TypeError (Python): Raised when an operation is applied to the wrong data type.

Handling each of these properly ensures a smooth user experience and a more robust application.

Catching multiple exceptions

Programs often need to catch more than one type of error. This can be done by:

  • Having multiple except or catch blocks.

  • Catching a general exception object.

Python example

try:
    # risky operation
except (ValueError, TypeError):
    print("A value or type error occurred.")

Java example

try {
    // risky operation
} catch (Exception e) {
    System.out.println("An error occurred: " + e.getMessage());
}

Note: Catching general exceptions should be done with caution, as it can hide specific issues.

Raising exceptions manually

Sometimes you want to raise an exception deliberately. This is useful for enforcing rules in your code.

Python:

if age < 0:
    raise ValueError("Age cannot be negative")

Java:

 if (age < 0) {
    throw new IllegalArgumentException("Age cannot be negative");
}

Using finally blocks

The finally block is used to specify code that should always run, such as:

  • Closing a file

  • Releasing a network connection

  • Cleaning up resources

Example in Python:

try:
    f = open("data.txt")
    # work with file
except FileNotFoundError:
    print("File not found.")
finally:
    f.close()

Even if the file is not found or an error occurs while working with it, the finally block will attempt to close it.

Real-world examples

Input validation

def get_number():
    while True:
        try:
            return int(input("Enter a number: "))
        except ValueError:
            print("That's not a valid number.")

Safe division

def safe_divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return "Cannot divide by zero."

File reading

try:
    with open("results.txt") as f:
        content = f.read()
except FileNotFoundError:
    print("The file doesn't exist.")

These examples demonstrate how exception handling makes your code more reliable and easier to maintain. It is essential for developing professional-quality software.

FAQ

A hardware random number generator (HRNG) produces truly random numbers using physical phenomena, such as electronic noise or radioactive decay. These generators rely on unpredictable, non-deterministic natural processes to generate values. HRNGs are typically found in specialised hardware devices and are used in high-security applications like cryptography, secure key generation, and secure communications where unpredictability is essential. In contrast, a pseudo-random number generator (PRNG) is an algorithm that generates a sequence of numbers that only appear to be random. PRNGs are deterministic, meaning they start from an initial seed and produce the same sequence each time that seed is reused. They are efficient and suitable for most general-purpose programming needs, such as simulations, games, and testing. However, PRNGs are not suitable for high-security tasks where true randomness is necessary. For A-Level purposes, PRNGs are the standard and are built into programming languages like Python, Java, and C#.

Exception handling becomes particularly crucial when a program combines file input/output (I/O) operations with random number generation. File operations can fail for reasons such as the file not existing, lacking read/write permissions, or the file being locked by another program. When a program reads user configuration values (like range limits for random number generation) from a file, failure to read those values correctly may result in invalid or undefined inputs. If these inputs are then used to generate random numbers—such as calling a random function with a lower bound higher than the upper bound—it can trigger runtime errors. Exception handling allows the program to gracefully catch these issues, whether they stem from missing files or invalid data formats, and respond appropriately, for example by notifying the user, logging the error, or using fallback defaults. This ensures the program remains stable and user-friendly even in the face of unexpected conditions.

Exception handling does introduce a slight overhead to program execution, but this is generally negligible in high-level languages unless exceptions are raised frequently during execution. The try-except structure itself is inexpensive, but the act of catching and handling exceptions can be slower than normal control flow. For this reason, exception handling should not be used as a substitute for regular conditional checks. It is best used for exceptional circumstances, not expected logic. For example, checking if an input is within a valid range should be done with an if statement rather than relying on an exception to catch an out-of-bounds error. Overusing exception handling for normal program flow can make the code harder to read, debug, and maintain. Best practice is to anticipate where errors are genuinely likely—such as user input, file operations, network calls—and apply targeted exception handling to those parts only, ensuring both clarity and reliability.

Yes, in many programming languages like Python, you can handle multiple exception types in a single except block using a tuple. For example:

except (ValueError, TypeError):

    print("Invalid input type.")

This approach is appropriate when the response to multiple potential exceptions is the same, thereby avoiding repetitive code. It improves readability and simplifies the error-handling logic. However, it’s important not to overgeneralise exceptions, as this can obscure the actual problem and make debugging more difficult. If different exceptions require distinct handling actions—such as one needing to log an error and another to retry an operation—then separate blocks should be used. Grouping should be reserved for scenarios where different exceptions are genuinely handled identically, such as catching both ValueError and TypeError when validating numerical input from a string. Careful grouping also helps avoid accidentally catching exceptions that should be handled separately or escalated, especially in complex applications.

Testing functions that incorporate both randomness and exception handling requires a combination of techniques to ensure thorough coverage. One common best practice is to seed the random number generator with a fixed value during testing. This ensures consistent outputs, allowing tests to be repeatable and reliable. By using a known seed, the sequence of random values becomes predictable, enabling you to write specific assertions against expected results. Secondly, you should deliberately trigger exceptions using test inputs that simulate edge cases, such as invalid user input or inputs that cause divide-by-zero or out-of-bounds errors. This confirms that the exception handling code is executed and behaves as expected. Additionally, use mocking techniques or dependency injection to replace random number generators with deterministic alternatives during tests. Finally, always test both the success path (when everything works) and the failure path (when errors occur) to verify that the program behaves correctly in all scenarios.

Practice Questions

Explain how pseudo-random number generation is used in programming and describe one situation where setting the seed would be useful.

Pseudo-random number generation uses deterministic algorithms to produce numbers that appear random. These numbers are not truly random as they are generated from an initial value known as a seed. The same seed always produces the same sequence, making outputs reproducible. This is useful in debugging or testing where consistent random sequences are required. For example, a game developer might use a fixed seed to ensure a level generates identically each time, helping them identify and fix specific issues without variability caused by random behaviour.

Describe how exception handling can improve the reliability of a program that uses user input and random numbers.

Exception handling allows a program to detect and respond to errors during execution without crashing. When dealing with user input, such as requesting the number of random values to generate, invalid data types or values can cause errors like ValueError or ZeroDivisionError. Using try-except blocks, the program can catch these errors and display meaningful messages, prompting the user to re-enter valid input. This ensures the program continues to run safely and predictably. It improves reliability by making the program robust against unexpected input and maintaining control over how errors are managed.

Hire a tutor

Please fill out the form and we'll find a tutor for you.

1/2
Your details
Alternatively contact us via
WhatsApp, Phone Call, or Email