Data Representation
AS Level — Unit 1: Fundamentals of Computer Science
Bit, Byte and Word
All data in a computer is ultimately represented as binary – patterns of 0s and 1s. The fundamental units of digital data are:
A bit (binary digit) is the smallest unit of data, holding a value of either 0 or 1. A byte is a group of 8 bits. A word is a fixed-size group of bits that the processor can handle as a single unit.
| Unit | Size | Notes |
|---|---|---|
| Bit | 1 binary digit | Either 0 or 1 |
| Nibble | 4 bits | Can represent one hexadecimal digit (0-F) |
| Byte | 8 bits | Can represent 2^8 = 256 different values (0-255 unsigned) |
| Word | Processor-dependent | Typically 16, 32, or 64 bits in modern systems |
The word length of a processor determines:
- How much data the CPU can process in a single operation
- The width of the data bus
- The size of the CPU’s general-purpose registers
- A 64-bit processor has a 64-bit word length, meaning it can process 64 bits of data per instruction
Larger data quantities use standard prefixes:
| Unit | Approximate Size | Exact Size (Binary) |
|---|---|---|
| Kilobyte (KB) | 1,000 bytes | 1,024 bytes (2^10) |
| Megabyte (MB) | 1,000,000 bytes | 1,048,576 bytes (2^20) |
| Gigabyte (GB) | 1,000,000,000 bytes | 1,073,741,824 bytes (2^30) |
| Terabyte (TB) | 1,000,000,000,000 bytes | 1,099,511,627,776 bytes (2^40) |
The word size varies between processors. A 32-bit processor has a word length of 32 bits (4 bytes), while a 64-bit processor has a word length of 64 bits (8 bytes). Word size affects the maximum value that can be processed in one operation and the maximum addressable memory.
Binary and Hexadecimal Number Systems
Binary (Base-2)
Binary uses only two digits: 0 and 1. Each position in a binary number represents a power of 2, increasing from right to left.
Place values for an 8-bit binary number:
| 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
|---|---|---|---|---|---|---|---|
| 2^7 | 2^6 | 2^5 | 2^4 | 2^3 | 2^2 | 2^1 | 2^0 |
Converting binary to decimal: Add together the place values where there is a 1.
Example: 10110101 = 128 + 0 + 32 + 16 + 0 + 4 + 0 + 1 = 181
Converting decimal to binary: Repeatedly divide by 2 and record the remainders (read from bottom to top), or subtract the largest possible power of 2 at each step.
Example: Convert 200 to binary:
- 200 - 128 = 72 (bit 7 = 1)
- 72 - 64 = 8 (bit 6 = 1)
- 8 - 8 = 0 (bit 3 = 1)
- Result: 11001000
Hexadecimal (Base-16)
Hexadecimal uses 16 symbols: 0-9 and A-F (where A=10, B=11, C=12, D=13, E=14, F=15). Each hex digit represents exactly 4 binary bits (one nibble).
| Decimal | Binary | Hex |
|---|---|---|
| 0 | 0000 | 0 |
| 1 | 0001 | 1 |
| 2 | 0010 | 2 |
| 3 | 0011 | 3 |
| 4 | 0100 | 4 |
| 5 | 0101 | 5 |
| 6 | 0110 | 6 |
| 7 | 0111 | 7 |
| 8 | 1000 | 8 |
| 9 | 1001 | 9 |
| 10 | 1010 | A |
| 11 | 1011 | B |
| 12 | 1100 | C |
| 13 | 1101 | D |
| 14 | 1110 | E |
| 15 | 1111 | F |
Why hexadecimal is used in computing:
- More compact than binary – one hex digit replaces four binary digits
- Easier for humans to read and remember than long binary strings
- Direct conversion to and from binary (each hex digit = 4 bits)
- Used for: memory addresses, colour codes (e.g., #FF5733), MAC addresses, error codes, machine code
Converting binary to hex: Split into groups of 4 bits (nibbles) from the right, then convert each group.
Example: 11010110 -> 1101 0110 -> D6
Converting hex to decimal: Multiply each digit by its place value (powers of 16).
Example: 3A = (3 x 16) + (10 x 1) = 48 + 10 = 58
# Number system conversions
decimal_value = 200
# Decimal to binary
binary_str = bin(decimal_value) # '0b11001000'
print(f"Decimal {decimal_value} = Binary {binary_str}")
print(f"Formatted: {decimal_value:08b}")
# Decimal to hexadecimal
hex_str = hex(decimal_value) # '0xc8'
print(f"Decimal {decimal_value} = Hex {hex_str}")
print(f"Formatted: {decimal_value:02X}")
# Binary to decimal
binary_input = "10110101"
decimal_result = int(binary_input, 2) # 181
print(f"Binary {binary_input} = Decimal {decimal_result}")
# Hex to decimal
hex_input = "D6"
decimal_result = int(hex_input, 16) # 214
print(f"Hex {hex_input} = Decimal {decimal_result}")
# Manual binary to decimal conversion
def binary_to_decimal(binary_string):
total = 0
for i, bit in enumerate(reversed(binary_string)):
if bit == '1':
total += 2 ** i
return total
print(f"Manual conversion: 10110101 = {binary_to_decimal('10110101')}")
' Number system conversions
Module NumberSystemsDemo
Sub Main()
Dim decimalValue As Integer = 200
' Decimal to binary
Dim binaryStr As String = Convert.ToString(decimalValue, 2).PadLeft(8, "0"c)
Console.WriteLine($"Decimal {decimalValue} = Binary {binaryStr}")
' Decimal to hexadecimal
Dim hexStr As String = decimalValue.ToString("X2")
Console.WriteLine($"Decimal {decimalValue} = Hex {hexStr}")
' Binary to decimal
Dim binaryInput As String = "10110101"
Dim decimalResult As Integer = Convert.ToInt32(binaryInput, 2)
Console.WriteLine($"Binary {binaryInput} = Decimal {decimalResult}")
' Hex to decimal
Dim hexInput As String = "D6"
decimalResult = Convert.ToInt32(hexInput, 16)
Console.WriteLine($"Hex {hexInput} = Decimal {decimalResult}")
' Manual binary to decimal conversion
Console.WriteLine($"Manual conversion: 10110101 = {BinaryToDecimal("10110101")}")
End Sub
Function BinaryToDecimal(binaryString As String) As Integer
Dim total As Integer = 0
For i As Integer = 0 To binaryString.Length - 1
If binaryString(binaryString.Length - 1 - i) = "1"c Then
total += CInt(2 ^ i)
End If
Next
Return total
End Function
End Module
Hexadecimal is a base-16 number system using digits 0-9 and letters A-F. It provides a compact, human-readable way to represent binary data, where each hexadecimal digit corresponds to exactly 4 binary bits.
You must be able to convert quickly and accurately between binary, decimal, and hexadecimal in all directions. Practise until these conversions are automatic. A common approach for hex-to-decimal is to convert hex to binary first, then binary to decimal.
Storage of Characters in Binary Form
Computers store all data – including text characters – as binary numbers. Each character (letter, digit, punctuation mark, control character) is assigned a unique numeric code according to a character set (also called a character encoding).
When you press a key on a keyboard, the character is converted to its binary code for storage and processing. When the character is displayed on screen, the binary code is converted back to a visual symbol using a font.
Example: The character A is stored as:
- ASCII code: 65 (decimal) =
01000001(binary) =41(hex) - The character
a(lowercase) is ASCII code 97 =01100001=61
Note that uppercase and lowercase letters have different codes. The difference between A (65) and a (97) is exactly 32 – this is by design and allows easy case conversion by flipping a single bit.
# Character to binary representation
for char in "Hello!":
code = ord(char)
print(f"'{char}' -> ASCII: {code:3d} -> Binary: {code:08b} -> Hex: {code:02X}")
# Case conversion using bit manipulation
upper_a = ord('A') # 65 = 01000001
lower_a = ord('a') # 97 = 01100001
print(f"\n'A' = {upper_a:08b}")
print(f"'a' = {lower_a:08b}")
print(f"Difference = {lower_a - upper_a} (bit 5 is toggled)")
# Convert uppercase to lowercase by setting bit 5
char = 'H'
lower = chr(ord(char) | 0b00100000) # Set bit 5
print(f"'{char}' -> '{lower}' (OR with 00100000)")
' Character to binary representation
Module CharacterDemo
Sub Main()
For Each c In "Hello!"
Dim code As Integer = Asc(c)
Console.WriteLine($"'{c}' -> ASCII: {code,3} -> Binary: {Convert.ToString(code, 2).PadLeft(8, "0"c)} -> Hex: {code:X2}")
Next
' Case conversion
Dim upperA As Integer = Asc("A"c) ' 65
Dim lowerA As Integer = Asc("a"c) ' 97
Console.WriteLine()
Console.WriteLine($"'A' = {Convert.ToString(upperA, 2).PadLeft(8, "0"c)}")
Console.WriteLine($"'a' = {Convert.ToString(lowerA, 2).PadLeft(8, "0"c)}")
Console.WriteLine($"Difference = {lowerA - upperA} (bit 5 is toggled)")
' Convert uppercase to lowercase by setting bit 5
Dim ch As Char = "H"c
Dim lowerCh As Char = Chr(Asc(ch) Or &B00100000)
Console.WriteLine($"'{ch}' -> '{lowerCh}' (OR with 00100000)")
End Sub
End Module
Standardised Character Sets
ASCII (American Standard Code for Information Interchange)
- Uses 7 bits per character, giving 128 possible characters (codes 0-127)
- Extended ASCII uses 8 bits (1 byte), giving 256 characters (codes 0-255)
- Covers uppercase and lowercase English letters, digits 0-9, punctuation, and control characters (e.g., newline, tab, backspace)
- Does not support characters from non-Latin scripts (Chinese, Arabic, Hindi, etc.)
| Code Range | Characters |
|---|---|
| 0-31 | Control characters (non-printable) |
| 32-47 | Space, punctuation, symbols |
| 48-57 | Digits 0-9 |
| 65-90 | Uppercase letters A-Z |
| 97-122 | Lowercase letters a-z |
| 128-255 | Extended ASCII (varies by encoding) |
Unicode
- A universal standard designed to represent every character from every writing system in the world
- Currently defines over 149,000 characters from 161 scripts
- Several encoding formats:
- UTF-8: Variable-length encoding using 1-4 bytes per character. ASCII characters use 1 byte, making it backward-compatible with ASCII. The most widely used encoding on the web
- UTF-16: Uses 2 or 4 bytes per character. Used internally by Windows and Java
- UTF-32: Uses a fixed 4 bytes per character. Simple but wasteful of storage
| Feature | ASCII | Unicode (UTF-8) |
|---|---|---|
| Bits per character | 7 (or 8 extended) | 8-32 (variable) |
| Characters supported | 128 (or 256) | Over 149,000 |
| Languages | English only | All world languages |
| Storage per English char | 1 byte | 1 byte (same as ASCII) |
| Storage per non-English char | Not supported | 2-4 bytes |
| Backward compatibility | N/A | UTF-8 is backward-compatible with ASCII |
ASCII is a 7-bit character encoding standard representing 128 characters, primarily English letters, digits, and symbols. Unicode is a universal standard that assigns a unique code point to every character from every writing system, with UTF-8 being its most common encoding.
ASCII uses 1 byte per character. Unicode (UTF-8) uses 1-4 bytes. This means Unicode files containing non-English text will be larger than equivalent ASCII files. However, for pure English text, UTF-8 and ASCII use the same amount of storage.
Primitive Data Types: Boolean, Character, String, Integer, Real
A data type defines what kind of value a variable can hold and what operations can be performed on it. Primitive data types (also called simple or elementary data types) are the basic building blocks provided by a programming language.
| Data Type | Description | Example Values |
|---|---|---|
| Boolean | Holds only two values: True or False |
True, False |
| Character | A single character (letter, digit, or symbol) | 'A', '7', '!' |
| String | A sequence of zero or more characters | "Hello", "", "42" |
| Integer | A whole number (positive, negative, or zero) | 42, -7, 0 |
| Real (Float) | A number with a fractional part (decimal point) | 3.14, -0.5, 2.0 |
# Demonstrating primitive data types
is_active = True # Boolean
grade = 'A' # Character (Python uses strings of length 1)
name = "Alice Smith" # String
age = 17 # Integer
height = 1.73 # Real (float)
print(f"Boolean: {is_active} -> type: {type(is_active).__name__}")
print(f"Character: {grade} -> type: {type(grade).__name__}")
print(f"String: {name} -> type: {type(name).__name__}")
print(f"Integer: {age} -> type: {type(age).__name__}")
print(f"Real: {height} -> type: {type(height).__name__}")
# Type matters for operations
print(f"\n'5' + '3' = {'5' + '3'}") # String concatenation: '53'
print(f" 5 + 3 = {5 + 3}") # Integer addition: 8
print(f" 5.0 + 3.0 = {5.0 + 3.0}") # Float addition: 8.0
' Demonstrating primitive data types
Module DataTypesDemo
Sub Main()
Dim isActive As Boolean = True ' Boolean
Dim grade As Char = "A"c ' Character
Dim name As String = "Alice Smith" ' String
Dim age As Integer = 17 ' Integer
Dim height As Double = 1.73 ' Real (Double)
Console.WriteLine($"Boolean: {isActive} -> type: {isActive.GetType().Name}")
Console.WriteLine($"Character: {grade} -> type: {grade.GetType().Name}")
Console.WriteLine($"String: {name} -> type: {name.GetType().Name}")
Console.WriteLine($"Integer: {age} -> type: {age.GetType().Name}")
Console.WriteLine($"Real: {height} -> type: {height.GetType().Name}")
' Type matters for operations
Console.WriteLine()
Console.WriteLine($"'5' & '3' = {"5" & "3"}") ' String concatenation: 53
Console.WriteLine($" 5 + 3 = {5 + 3}") ' Integer addition: 8
Console.WriteLine($" 5.0 + 3.0 = {5.0 + 3.0}") ' Double addition: 8.0
End Sub
End Module
Primitive data types are the simplest data types provided by a programming language, from which more complex data structures are built. They include Boolean, character, string, integer, and real (floating point).
Storage Requirements for Each Data Type
The amount of memory required to store a value depends on its data type. Different languages and systems may use slightly different sizes, but typical values are:
| Data Type | Typical Storage | Range / Capacity |
|---|---|---|
| Boolean | 1 bit (often stored as 1 byte for alignment) | True or False |
| Character (ASCII) | 1 byte (8 bits) | 256 characters |
| Character (Unicode) | 1-4 bytes (UTF-8) or 2 bytes (UTF-16) | Over 149,000 characters |
| Short Integer | 2 bytes (16 bits) | -32,768 to 32,767 (signed) |
| Integer | 4 bytes (32 bits) | -2,147,483,648 to 2,147,483,647 (signed) |
| Long Integer | 8 bytes (64 bits) | Approximately -9.2 x 10^18 to 9.2 x 10^18 |
| Single (float) | 4 bytes (32 bits) | Approximately +/-3.4 x 10^38 (7 significant digits) |
| Double | 8 bytes (64 bits) | Approximately +/-1.7 x 10^308 (15 significant digits) |
| String | 1 byte per character (ASCII) + overhead | Variable length |
Calculating Storage Requirements
To calculate the storage needed for a data set:
Example: A database stores 1000 student records, each containing:
- Name (30 characters, ASCII): 30 bytes
- Student ID (integer): 4 bytes
- Average mark (real/double): 8 bytes
- Active (boolean): 1 byte
Storage per record: 30 + 4 + 8 + 1 = 43 bytes Total for 1000 records: 43 x 1000 = 43,000 bytes (approximately 42 KB)
# Calculating storage requirements
import sys
# Python's internal sizes (may differ from theoretical minimums)
print("Python object sizes:")
print(f" Boolean: {sys.getsizeof(True)} bytes (Python overhead)")
print(f" Integer: {sys.getsizeof(42)} bytes (Python overhead)")
print(f" Float: {sys.getsizeof(3.14)} bytes (Python overhead)")
print(f" Char: {sys.getsizeof('A')} bytes (Python overhead)")
print(f" String(5): {sys.getsizeof('Hello')} bytes (Python overhead)")
# Theoretical storage calculation
print("\nTheoretical storage for 1000 student records:")
name_bytes = 30 # 30 ASCII characters
id_bytes = 4 # 32-bit integer
mark_bytes = 8 # 64-bit double
active_bytes = 1 # Boolean
record_size = name_bytes + id_bytes + mark_bytes + active_bytes
total_size = record_size * 1000
print(f" Per record: {record_size} bytes")
print(f" 1000 records: {total_size} bytes ({total_size / 1024:.1f} KB)")
' Calculating storage requirements
Module StorageDemo
Sub Main()
' VB.NET data type sizes
Console.WriteLine("VB.NET data type sizes:")
Console.WriteLine($" Boolean: {Len(True)} byte(s)")
Console.WriteLine($" Char: {Len("A"c)} byte(s)")
Console.WriteLine($" Short: {Len(CShort(0))} bytes")
Console.WriteLine($" Integer: {Len(CInt(0))} bytes")
Console.WriteLine($" Long: {Len(CLng(0))} bytes")
Console.WriteLine($" Single: {Len(CSng(0))} bytes")
Console.WriteLine($" Double: {Len(CDbl(0))} bytes")
' Theoretical storage calculation
Console.WriteLine()
Console.WriteLine("Theoretical storage for 1000 student records:")
Dim nameBytes As Integer = 30 ' 30 ASCII characters
Dim idBytes As Integer = 4 ' Integer
Dim markBytes As Integer = 8 ' Double
Dim activeBytes As Integer = 1 ' Boolean
Dim recordSize As Integer = nameBytes + idBytes + markBytes + activeBytes
Dim totalSize As Integer = recordSize * 1000
Console.WriteLine($" Per record: {recordSize} bytes")
Console.WriteLine($" 1000 records: {totalSize} bytes ({totalSize / 1024:F1} KB)")
End Sub
End Module
When calculating storage requirements in exams, clearly show your working: list each field, its data type, and its byte size, then multiply by the number of records. Remember that a string of n ASCII characters requires n bytes.
Binary Arithmetic Techniques
Binary Addition
Binary addition follows the same principles as decimal addition, with the following rules:
| A | B | Sum | Carry |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 0 | 1 | 1 | 0 |
| 1 | 0 | 1 | 0 |
| 1 | 1 | 0 | 1 |
When adding three bits (including a carry): 1 + 1 + 1 = 1 with a carry of 1.
Example: Add 01101011 + 00110101
1 1 1 1 1 (carry bits)
0 1 1 0 1 0 1 1 (107)
+ 0 0 1 1 0 1 0 1 ( 53)
-------------------
1 0 1 0 0 0 0 0 (160)
Binary Subtraction
Binary subtraction can be performed by adding the two’s complement of the number to be subtracted (see the next section). Alternatively, the direct subtraction rules are:
| A | B | Difference | Borrow |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 1 | 0 | 1 | 0 |
| 1 | 1 | 0 | 0 |
| 0 | 1 | 1 | 1 (borrow from next column) |
Binary Multiplication
Binary multiplication is simpler than decimal multiplication because you only multiply by 0 or 1:
Example: 1101 x 1011 (13 x 11 = 143)
1 1 0 1 (13)
x 1 0 1 1 (11)
-----------
1 1 0 1 (1101 x 1)
1 1 0 1 0 (1101 x 1, shifted left 1)
0 0 0 0 0 0 0 (1101 x 0, shifted left 2)
1 1 0 1 0 0 0 0 (1101 x 1, shifted left 3)
---------------
1 0 0 0 1 1 1 1 (143)
Logical Shifts
A logical shift moves all bits left or right by a specified number of positions. Empty positions are filled with zeros.
- Left shift by 1: Multiplies by 2 (each bit moves to a higher place value)
- Right shift by 1: Divides by 2 (integer division; each bit moves to a lower place value)
- Left shift by n: Multiplies by 2^n
- Right shift by n: Divides by 2^n
Example: Logical left shift of 00010110 (22) by 2 positions:
00010110->01011000(88) – multiplied by 4
Example: Logical right shift of 00010110 (22) by 1 position:
00010110->00001011(11) – divided by 2
# Binary arithmetic operations
a = 0b01101011 # 107
b = 0b00110101 # 53
# Addition
result_add = a + b
print(f"Addition: {a:08b} ({a}) + {b:08b} ({b}) = {result_add:08b} ({result_add})")
# Subtraction
result_sub = a - b
print(f"Subtraction: {a:08b} ({a}) - {b:08b} ({b}) = {result_sub:08b} ({result_sub})")
# Multiplication
c = 0b1101 # 13
d = 0b1011 # 11
result_mul = c * d
print(f"Multiplication: {c:04b} ({c}) x {d:04b} ({d}) = {result_mul:08b} ({result_mul})")
# Logical shifts
value = 0b00010110 # 22
left_shift = value << 2 # Multiply by 4
right_shift = value >> 1 # Divide by 2
print(f"\nShifts on {value:08b} ({value}):")
print(f" Left shift by 2: {left_shift:08b} ({left_shift})")
print(f" Right shift by 1: {right_shift:08b} ({right_shift})")
' Binary arithmetic operations
Module BinaryArithmeticDemo
Sub Main()
Dim a As Integer = &B01101011 ' 107
Dim b As Integer = &B00110101 ' 53
' Addition
Dim resultAdd As Integer = a + b
Console.WriteLine($"Addition: {Convert.ToString(a, 2).PadLeft(8, "0"c)} ({a}) + {Convert.ToString(b, 2).PadLeft(8, "0"c)} ({b}) = {Convert.ToString(resultAdd, 2).PadLeft(8, "0"c)} ({resultAdd})")
' Subtraction
Dim resultSub As Integer = a - b
Console.WriteLine($"Subtraction: {Convert.ToString(a, 2).PadLeft(8, "0"c)} ({a}) - {Convert.ToString(b, 2).PadLeft(8, "0"c)} ({b}) = {Convert.ToString(resultSub, 2).PadLeft(8, "0"c)} ({resultSub})")
' Multiplication
Dim c As Integer = &B1101 ' 13
Dim d As Integer = &B1011 ' 11
Dim resultMul As Integer = c * d
Console.WriteLine($"Multiplication: {Convert.ToString(c, 2).PadLeft(4, "0"c)} ({c}) x {Convert.ToString(d, 2).PadLeft(4, "0"c)} ({d}) = {Convert.ToString(resultMul, 2).PadLeft(8, "0"c)} ({resultMul})")
' Logical shifts
Dim value As Integer = &B00010110 ' 22
Dim leftShift As Integer = value << 2
Dim rightShift As Integer = value >> 1
Console.WriteLine()
Console.WriteLine($"Shifts on {Convert.ToString(value, 2).PadLeft(8, "0"c)} ({value}):")
Console.WriteLine($" Left shift by 2: {Convert.ToString(leftShift, 2).PadLeft(8, "0"c)} ({leftShift})")
Console.WriteLine($" Right shift by 1: {Convert.ToString(rightShift, 2).PadLeft(8, "0"c)} ({rightShift})")
End Sub
End Module
When performing binary addition in an exam, write the carry bits above each column to keep track. Overflow occurs when the result is too large to fit in the available number of bits – for example, adding two positive 8-bit numbers that produce a 9-bit result.
Two’s Complement and Sign and Magnitude Representation
Computers need to represent both positive and negative integers. There are two main methods.
Sign and Magnitude
- The most significant bit (MSB) represents the sign:
0= positive,1= negative - The remaining bits represent the magnitude (absolute value) of the number
- Simple for humans to understand
Example (8 bits):
+42=00101010(MSB is 0 = positive, remaining 7 bits = 42)-42=10101010(MSB is 1 = negative, remaining 7 bits = 42)
Problems with sign and magnitude:
- There are two representations of zero:
00000000(+0) and10000000(-0) - Arithmetic is complicated – the processor must check signs before performing addition/subtraction
- Range for 8 bits: -127 to +127
Two’s Complement
Two’s complement is the standard method used in modern computers to represent signed integers.
- The MSB has a negative place value: -128 for an 8-bit number
- All other bits have their usual positive place values
- Only one representation of zero (
00000000) - Arithmetic is straightforward – addition and subtraction work normally without checking signs
Converting positive to negative (and vice versa):
- Write the positive number in binary
- Invert all bits (change 0 to 1 and 1 to 0) – this gives the one’s complement
- Add 1 to the result
Example: Convert +42 to -42 in 8-bit two’s complement:
- +42 =
00101010 - Invert:
11010101 - Add 1:
11010110
So -42 = 11010110
Verify: -128 + 64 + 16 + 4 + 2 = -128 + 86 = -42
Range for n bits:
- Minimum:
-2^(n-1)(e.g., -128 for 8 bits) - Maximum:
2^(n-1) - 1(e.g., +127 for 8 bits) - Total values: 2^n
| Feature | Sign and Magnitude | Two’s Complement |
|---|---|---|
| MSB meaning | Sign bit (0=+, 1=-) | Negative place value |
| Zero representations | Two (+0 and -0) | One (0 only) |
| 8-bit range | -127 to +127 | -128 to +127 |
| Arithmetic | Complex (must check signs) | Simple (standard addition works) |
| Modern usage | Rarely used | Standard in all modern processors |
# Two's complement operations
def to_twos_complement(value, bits=8):
"""Convert a signed integer to two's complement binary string."""
if value >= 0:
return format(value, f'0{bits}b')
else:
# Two's complement for negative numbers
return format((1 << bits) + value, f'0{bits}b')
def from_twos_complement(binary_str):
"""Convert a two's complement binary string to a signed integer."""
bits = len(binary_str)
value = int(binary_str, 2)
if binary_str[0] == '1': # Negative number
value -= (1 << bits)
return value
# Demonstrate conversions
for num in [42, -42, 127, -128, 0, -1]:
tc = to_twos_complement(num)
back = from_twos_complement(tc)
print(f"{num:>5d} -> {tc} -> {back:>5d}")
# Show the invert-and-add-one process
positive = 42
binary = format(positive, '08b')
inverted = ''.join('1' if b == '0' else '0' for b in binary)
result = format(int(inverted, 2) + 1, '08b')
print(f"\nConverting +{positive} to negative:")
print(f" Original: {binary} ({positive})")
print(f" Inverted: {inverted}")
print(f" Add 1: {result} ({from_twos_complement(result)})")
' Two's complement operations
Module TwosComplementDemo
Function ToTwosComplement(value As Integer, Optional bits As Integer = 8) As String
If value >= 0 Then
Return Convert.ToString(value, 2).PadLeft(bits, "0"c)
Else
' Two's complement for negative numbers
Return Convert.ToString((1 << bits) + value, 2).PadLeft(bits, "0"c)
End If
End Function
Function FromTwosComplement(binaryStr As String) As Integer
Dim bits As Integer = binaryStr.Length
Dim value As Integer = Convert.ToInt32(binaryStr, 2)
If binaryStr(0) = "1"c Then ' Negative number
value -= (1 << bits)
End If
Return value
End Function
Sub Main()
' Demonstrate conversions
Dim numbers() As Integer = {42, -42, 127, -128, 0, -1}
For Each num In numbers
Dim tc As String = ToTwosComplement(num)
Dim back As Integer = FromTwosComplement(tc)
Console.WriteLine($"{num,5} -> {tc} -> {back,5}")
Next
' Show the invert-and-add-one process
Dim positive As Integer = 42
Dim binary As String = Convert.ToString(positive, 2).PadLeft(8, "0"c)
Dim inverted As String = ""
For Each b In binary
inverted &= If(b = "0"c, "1", "0")
Next
Dim result As String = Convert.ToString(Convert.ToInt32(inverted, 2) + 1, 2).PadLeft(8, "0"c)
Console.WriteLine($"{vbCrLf}Converting +{positive} to negative:")
Console.WriteLine($" Original: {binary} ({positive})")
Console.WriteLine($" Inverted: {inverted}")
Console.WriteLine($" Add 1: {result} ({FromTwosComplement(result)})")
End Sub
End Module
Two’s complement is a method for representing signed integers where the most significant bit has a negative place value. To negate a number: invert all bits and add 1.
Common exam task: convert a negative decimal to two’s complement binary. Always state the number of bits you are using. Remember that the “invert and add 1” trick works in both directions – it converts positive to negative AND negative to positive.
Floating Point Form: Nature and Uses
Why Floating Point Is Needed
Integers can only represent whole numbers. To represent real numbers (numbers with fractional parts, like 3.14 or -0.001), computers use floating point representation.
Floating point is analogous to scientific notation in decimal:
- Decimal scientific notation: 6.022 x 10^23
- Binary floating point: mantissa x 2^exponent
Structure of a Floating Point Number
A floating point number is stored in two parts:
- Mantissa (M): Contains the significant digits of the number. Determines the precision (how many significant figures can be stored).
- Exponent (E): Indicates how far and in which direction to shift the binary point. Determines the range (how large or small the number can be).
Both the mantissa and exponent are typically stored in two’s complement form.
The value represented is: M x 2^E
Normalisation
A floating point number is normalised to ensure a unique representation for every value and to maximise the precision of the mantissa.
Rules for normalisation:
- For a positive number: the mantissa must begin with
0.1...(the first two bits are01) - For a negative number: the mantissa must begin with
1.0...(the first two bits are10)
If the mantissa does not follow these rules, shift it left or right and adjust the exponent accordingly.
Example: Normalise 0.00101 with exponent 0101 (5):
- Shift mantissa left by 2 to get
0.10100 - Decrease exponent by 2:
0101-0010=0011(3) - Normalised: mantissa =
0.10100, exponent =0011
Worked Example: Converting Decimal to Floating Point
Convert 6.75 to normalised floating point with 8-bit mantissa and 4-bit exponent (both in two’s complement).
- Convert 6.75 to binary:
110.11(4 + 2 + 0.5 + 0.25 = 6.75) - Normalise so mantissa starts with
0.1:0.110110x 2^3 - Mantissa (8 bits):
01101100 - Exponent (4 bits):
0011(3 in two’s complement) - Stored as:
01101100 0011
Worked Example: Converting Floating Point to Decimal
Given: mantissa = 01011000, exponent = 0100 (both two’s complement)
- Exponent =
0100= 4 - Starting with mantissa
0.1011000, shift binary point 4 places right:01011.000 - Convert to decimal: 8 + 2 + 1 = 11.0
# Floating point representation simulation
def decimal_to_float_parts(value, mantissa_bits=8, exponent_bits=4):
"""Demonstrate the concept of floating point decomposition."""
if value == 0:
return "0" * mantissa_bits, "0" * exponent_bits
# Determine sign
negative = value < 0
value = abs(value)
# Convert to binary
integer_part = int(value)
fractional_part = value - integer_part
# Integer part to binary
int_binary = bin(integer_part)[2:] if integer_part > 0 else "0"
# Fractional part to binary
frac_binary = ""
for _ in range(mantissa_bits):
fractional_part *= 2
if fractional_part >= 1:
frac_binary += "1"
fractional_part -= 1
else:
frac_binary += "0"
full_binary = int_binary + "." + frac_binary
print(f" Binary: {full_binary}")
# Count shifts needed to normalise (move point after leading 0)
exponent = len(int_binary)
# Build normalised mantissa
mantissa = "0" + int_binary + frac_binary
mantissa = mantissa[:mantissa_bits]
if negative:
print(f" (Negative number - mantissa would use two's complement)")
print(f" Normalised mantissa: 0.{mantissa[1:]}")
print(f" Exponent: {exponent}")
return mantissa, format(exponent, f'0{exponent_bits}b')
# Examples
for value in [6.75, 11.0, 0.375, -3.5]:
print(f"\nConverting {value}:")
m, e = decimal_to_float_parts(value)
print(f" Mantissa bits: {m}")
print(f" Exponent bits: {e}")
' Floating point representation simulation
Module FloatingPointDemo
Sub DecimalToFloatParts(value As Double, Optional mantissaBits As Integer = 8, Optional exponentBits As Integer = 4)
Console.WriteLine($"Converting {value}:")
Dim negative As Boolean = value < 0
Dim absValue As Double = Math.Abs(value)
' Integer part to binary
Dim intPart As Integer = CInt(Math.Floor(absValue))
Dim fracPart As Double = absValue - intPart
Dim intBinary As String = Convert.ToString(intPart, 2)
' Fractional part to binary
Dim fracBinary As String = ""
Dim tempFrac As Double = fracPart
For i As Integer = 1 To mantissaBits
tempFrac *= 2
If tempFrac >= 1 Then
fracBinary &= "1"
tempFrac -= 1
Else
fracBinary &= "0"
End If
Next
Console.WriteLine($" Binary: {intBinary}.{fracBinary}")
Dim exponent As Integer = intBinary.Length
Dim mantissa As String = ("0" & intBinary & fracBinary).Substring(0, mantissaBits)
If negative Then
Console.WriteLine(" (Negative number - mantissa would use two's complement)")
End If
Console.WriteLine($" Normalised mantissa: 0.{mantissa.Substring(1)}")
Console.WriteLine($" Exponent: {exponent}")
Console.WriteLine($" Mantissa bits: {mantissa}")
Console.WriteLine($" Exponent bits: {Convert.ToString(exponent, 2).PadLeft(exponentBits, "0"c)}")
End Sub
Sub Main()
DecimalToFloatParts(6.75)
Console.WriteLine()
DecimalToFloatParts(11.0)
Console.WriteLine()
DecimalToFloatParts(0.375)
Console.WriteLine()
DecimalToFloatParts(-3.5)
End Sub
End Module
Floating point representation stores a real number as a mantissa (the significant digits) and an exponent (the power of 2), allowing both very large and very small numbers to be represented. Normalisation ensures the mantissa starts with 01 (positive) or 10 (negative) to maximise precision.
Advantages/Disadvantages of Integer vs Floating Point
Choosing between integer and floating point representation depends on the requirements of the application.
| Feature | Integer | Floating Point |
|---|---|---|
| Values represented | Whole numbers only | Numbers with fractional parts |
| Precision | Exact (within range) | May have rounding errors |
| Range | Limited (e.g., -2 billion to +2 billion for 32-bit) | Very large (e.g., up to ~10^308 for 64-bit double) |
| Speed | Faster arithmetic | Slower arithmetic |
| Storage | Fixed and predictable | Fixed but complex internal structure |
| Use cases | Counting, indexing, loops, exact values | Scientific calculations, measurements, graphics |
Advantages of integer representation:
- Exact – no rounding errors; 5 + 3 is always exactly 8
- Faster – integer arithmetic is simpler for the processor
- Simpler – straightforward binary representation
- Best for: counting items, loop counters, array indices, currency (stored in pence/cents)
Disadvantages of integer representation:
- Cannot represent fractional values
- Limited range compared to floating point with the same number of bits
Advantages of floating point representation:
- Can represent fractional values and very large/small numbers
- Wide range of values
- Suitable for scientific and engineering calculations
Disadvantages of floating point representation:
- Rounding errors – not all real numbers can be represented exactly
- Slower – floating point operations are more complex
- Comparisons are unreliable – two floating point numbers that should be equal may differ slightly due to rounding
# Demonstrating integer precision vs floating point rounding
# Integer: exact
total_int = 0
for i in range(10):
total_int += 1
print(f"Integer sum of ten 1s: {total_int}") # Exactly 10
# Floating point: potential rounding error
total_float = 0.0
for i in range(10):
total_float += 0.1
print(f"Float sum of ten 0.1s: {total_float}") # Not exactly 1.0!
print(f"Is it exactly 1.0? {total_float == 1.0}") # False!
print(f"Actual value: {total_float:.20f}")
# Why 0.1 cannot be represented exactly in binary
# 0.1 in binary is 0.0001100110011... (repeating), like 1/3 in decimal
' Demonstrating integer precision vs floating point rounding
Module IntVsFloatDemo
Sub Main()
' Integer: exact
Dim totalInt As Integer = 0
For i As Integer = 1 To 10
totalInt += 1
Next
Console.WriteLine($"Integer sum of ten 1s: {totalInt}") ' Exactly 10
' Floating point: potential rounding error
Dim totalFloat As Double = 0.0
For i As Integer = 1 To 10
totalFloat += 0.1
Next
Console.WriteLine($"Float sum of ten 0.1s: {totalFloat}")
Console.WriteLine($"Is it exactly 1.0? {totalFloat = 1.0}")
Console.WriteLine($"Actual value: {totalFloat:F20}")
' Comparison issue
Dim a As Double = 0.1 + 0.2
Dim b As Double = 0.3
Console.WriteLine($"{vbCrLf}0.1 + 0.2 = {a:F20}")
Console.WriteLine($" 0.3 = {b:F20}")
Console.WriteLine($"Are they equal? {a = b}")
End Sub
End Module
A classic exam question: “Why might 0.1 + 0.2 not equal 0.3 in a program?” Answer: because 0.1 cannot be represented exactly in binary floating point (it is a repeating binary fraction), so small rounding errors accumulate. This is why you should never compare floating point numbers for exact equality.
Convert Between Real Number and Floating Point Form
Decimal to Normalised Floating Point
Example: Convert -5.5 to normalised floating point with a 8-bit mantissa and 4-bit exponent (both two’s complement).
Step 1: Convert the positive value to binary.
- 5.5 =
101.1in binary
Step 2: Normalise (positive form first).
0.1011x 2^3 (shift binary point 3 places left)- Mantissa (positive):
01011000
Step 3: Negate the mantissa (because the original number is negative).
- Invert
01011000->10100111 - Add 1:
10101000 - Mantissa:
10101000
Step 4: Exponent = 3 = 0011 in 4-bit two’s complement.
Result: 10101000 0011
Verify: Start with mantissa 10101000:
- This is negative (starts with 1). Value = -128 + 32 + 8 = -88 out of 128 = -88/128 = -0.6875
- Exponent = 3, so multiply by 2^3 = 8
- -0.6875 x 8 = -5.5
Floating Point to Decimal
Example: Convert mantissa 01100000, exponent 0101 to decimal.
Step 1: Determine the exponent value.
0101= 5
Step 2: Write the mantissa with the binary point after the first bit.
0.1100000
Step 3: Shift the binary point right by 5 positions.
011000.00
Step 4: Convert to decimal.
011000= 16 + 8 = 24
Example with negative mantissa: mantissa 10110000, exponent 0011
Step 1: Exponent = 0011 = 3
Step 2: The mantissa starts with 1, so it is negative (two’s complement).
1.0110000
Step 3: Shift the binary point right by 3.
1011.0000
Step 4: Convert from two’s complement: -8 + 2 + 1 = -5
# Converting between decimal and floating point representation
def decimal_to_normalised_fp(value, m_bits=8, e_bits=4):
"""Convert decimal to normalised floating point."""
if value == 0:
return "0" * m_bits, "0" * e_bits, 0
negative = value < 0
abs_val = abs(value)
# Convert to binary string with fractional part
int_part = int(abs_val)
frac_part = abs_val - int_part
int_bin = bin(int_part)[2:] if int_part > 0 else ""
frac_bin = ""
temp = frac_part
for _ in range(m_bits * 2):
temp *= 2
if temp >= 1:
frac_bin += "1"
temp -= 1
else:
frac_bin += "0"
if temp == 0:
break
if int_part > 0:
exponent = len(int_bin)
mantissa_digits = int_bin + frac_bin
else:
# Find first 1 in fractional part
first_one = frac_bin.index('1')
exponent = -(first_one)
mantissa_digits = frac_bin[first_one:]
# Build mantissa: "0" + significant digits, padded to m_bits
mantissa_str = "0" + mantissa_digits
mantissa_str = mantissa_str[:m_bits].ljust(m_bits, '0')
if negative:
# Two's complement of mantissa
val = int(mantissa_str, 2)
val = (1 << m_bits) - val
mantissa_str = format(val, f'0{m_bits}b')
# Exponent in two's complement
if exponent < 0:
exp_str = format((1 << e_bits) + exponent, f'0{e_bits}b')
else:
exp_str = format(exponent, f'0{e_bits}b')
return mantissa_str, exp_str, exponent
# Test conversions
test_values = [6.75, -5.5, 11.0, 0.375]
for val in test_values:
m, e, exp_dec = decimal_to_normalised_fp(val)
print(f"{val:>7} -> Mantissa: {m} Exponent: {e} ({exp_dec})")
' Converting between decimal and floating point representation
Module FPConversionDemo
Sub ConvertToFP(value As Double, Optional mBits As Integer = 8, Optional eBits As Integer = 4)
Console.Write($"{value,7} -> ")
Dim negative As Boolean = value < 0
Dim absVal As Double = Math.Abs(value)
' Convert integer part to binary
Dim intPart As Integer = CInt(Math.Floor(absVal))
Dim fracPart As Double = absVal - intPart
Dim intBin As String = If(intPart > 0, Convert.ToString(intPart, 2), "")
' Convert fractional part to binary
Dim fracBin As String = ""
Dim temp As Double = fracPart
For i As Integer = 1 To mBits * 2
temp *= 2
If temp >= 1 Then
fracBin &= "1"
temp -= 1
Else
fracBin &= "0"
End If
If temp = 0 Then Exit For
Next
Dim exponent As Integer = intBin.Length
' Build mantissa
Dim mantissa As String = ("0" & intBin & fracBin)
If mantissa.Length > mBits Then
mantissa = mantissa.Substring(0, mBits)
Else
mantissa = mantissa.PadRight(mBits, "0"c)
End If
If negative Then
' Two's complement
Dim val As Integer = Convert.ToInt32(mantissa, 2)
val = (1 << mBits) - val
mantissa = Convert.ToString(val, 2).PadLeft(mBits, "0"c)
End If
Dim expStr As String
If exponent < 0 Then
expStr = Convert.ToString((1 << eBits) + exponent, 2).PadLeft(eBits, "0"c)
Else
expStr = Convert.ToString(exponent, 2).PadLeft(eBits, "0"c)
End If
Console.WriteLine($"Mantissa: {mantissa} Exponent: {expStr} ({exponent})")
End Sub
Sub Main()
ConvertToFP(6.75)
ConvertToFP(-5.5)
ConvertToFP(11.0)
ConvertToFP(0.375)
End Sub
End Module
When converting between decimal and floating point in exams, always clearly show: (1) the binary conversion, (2) the normalisation step, (3) the final mantissa and exponent in the specified format. State whether you are using two’s complement for both parts. Verify your answer by converting back.
Truncation and Rounding, Effect on Accuracy
When a number cannot be represented exactly in the available number of bits, it must be approximated. There are two main methods: truncation and rounding.
Truncation
Truncation simply discards (cuts off) the digits or bits beyond the available precision. No adjustment is made to the remaining digits.
Example (decimal): Storing 3.14159 with 2 decimal places by truncation gives 3.14 (the remaining digits are simply dropped).
Example (binary): If the mantissa has 8 bits and the true value requires 12 bits:
- Full value:
0.10110111 0101 - Truncated to 8 bits:
01011011 - The bits
0101are lost
Truncation always rounds towards zero. Positive numbers become slightly smaller; negative numbers become slightly closer to zero (less negative).
Rounding
Rounding adjusts the last retained bit based on the value of the first discarded bit. If the first discarded bit is 1, the last retained bit is rounded up (incremented by 1). If it is 0, the value is left unchanged.
Example (decimal): Storing 3.14159 with 2 decimal places by rounding gives 3.14 (since the third decimal digit, 1, is less than 5).
Example (binary): Truncated mantissa: 01011011, first discarded bit: 1
- Round up:
01011011+1=01011100
Rounding generally produces a more accurate result than truncation because it finds the closest representable value.
Comparison
| Aspect | Truncation | Rounding |
|---|---|---|
| Method | Discard excess bits | Adjust based on first discarded bit |
| Direction of error | Always towards zero | Closest representable value |
| Accuracy | Less accurate | More accurate |
| Simplicity | Simpler to implement | Slightly more complex |
| Bias | Systematic bias (always reduces magnitude) | Less biased (errors can go either way) |
Effect on Accuracy
Both truncation and rounding introduce representation errors (also called quantisation errors). These errors can have significant effects:
- Accumulation: Small errors can accumulate over many calculations, leading to large inaccuracies in final results
- Loss of significance: When subtracting two nearly equal floating point numbers, the significant digits cancel out and the result may be dominated by rounding errors
- Comparison failures: Two values that should be mathematically equal may differ slightly due to rounding, making direct comparison unreliable
Precision vs Range Trade-off
Given a fixed total number of bits for a floating point number:
- More mantissa bits -> higher precision (more significant figures) but smaller range
- More exponent bits -> larger range (bigger and smaller numbers) but lower precision
This fundamental trade-off means that no single floating point format is perfect for all applications. Different applications may require different balances:
- Scientific computing often needs high precision (many mantissa bits)
- Graphics rendering may need large range (many exponent bits)
# Demonstrating truncation vs rounding
import math
# Decimal example
value = 3.14159265
# Truncation (towards zero)
def truncate(number, decimals):
factor = 10 ** decimals
return int(number * factor) / factor
# Rounding
truncated = truncate(value, 2)
rounded = round(value, 2)
print(f"Original value: {value}")
print(f"Truncated to 2dp: {truncated}")
print(f"Rounded to 2dp: {rounded}")
print(f"Truncation error: {abs(value - truncated):.10f}")
print(f"Rounding error: {abs(value - rounded):.10f}")
# Accumulation of errors
print("\nError accumulation over 1000 additions of 0.1:")
total = 0.0
for _ in range(1000):
total += 0.1
expected = 100.0
print(f" Expected: {expected}")
print(f" Actual: {total:.15f}")
print(f" Error: {abs(total - expected):.15f}")
# Loss of significance
a = 1.000000001
b = 1.000000000
print(f"\nLoss of significance:")
print(f" a = {a:.12f}")
print(f" b = {b:.12f}")
print(f" a - b = {a - b:.15f} (expected 0.000000001)")
' Demonstrating truncation vs rounding
Module AccuracyDemo
Function Truncate(number As Double, decimals As Integer) As Double
Dim factor As Double = 10 ^ decimals
Return Math.Floor(number * factor) / factor
End Function
Sub Main()
Dim value As Double = 3.14159265
' Truncation vs rounding
Dim truncated As Double = Truncate(value, 2)
Dim rounded As Double = Math.Round(value, 2)
Console.WriteLine($"Original value: {value}")
Console.WriteLine($"Truncated to 2dp: {truncated}")
Console.WriteLine($"Rounded to 2dp: {rounded}")
Console.WriteLine($"Truncation error: {Math.Abs(value - truncated):F10}")
Console.WriteLine($"Rounding error: {Math.Abs(value - rounded):F10}")
' Accumulation of errors
Console.WriteLine()
Console.WriteLine("Error accumulation over 1000 additions of 0.1:")
Dim total As Double = 0.0
For i As Integer = 1 To 1000
total += 0.1
Next
Dim expected As Double = 100.0
Console.WriteLine($" Expected: {expected}")
Console.WriteLine($" Actual: {total:F15}")
Console.WriteLine($" Error: {Math.Abs(total - expected):F15}")
' Loss of significance
Dim a As Double = 1.000000001
Dim b As Double = 1.0
Console.WriteLine()
Console.WriteLine("Loss of significance:")
Console.WriteLine($" a = {a:F12}")
Console.WriteLine($" b = {b:F12}")
Console.WriteLine($" a - b = {(a - b):F15} (expected 0.000000001)")
End Sub
End Module
Truncation discards excess bits without adjustment, always introducing an error towards zero. Rounding adjusts the last retained bit based on the discarded bits, producing a closer approximation. Both introduce errors, but rounding is generally more accurate.
When discussing floating point accuracy, always mention: (1) the trade-off between mantissa bits (precision) and exponent bits (range), (2) that truncation/rounding errors can accumulate over many calculations, and (3) that this is why floating point numbers should never be compared for exact equality. Give the example of 0.1 + 0.2 != 0.3.