Numbers and Number Sets
Natural numbers, integers, rationals and reals, the order of operations, fractions, percentages, rounding and scientific notation.
The number sets
Numbers come in families. Each family was invented because the previous one could not answer some question: you cannot subtract 5 from 3 with only counting numbers, and you cannot divide 1 by 3 with only whole numbers. Mathematicians give each family a name and a letter in "blackboard bold", a font with doubled strokes.
Natural numbers
The natural numbers are the counting numbers, including zero:
The curly brackets mean "the set of", and the dots mean "and so on, forever". Natural numbers count things: tickets in a queue, disks in a server, bits in a byte. Adding or multiplying two natural numbers always gives a natural number again, but subtracting does not: 3 − 5 has no answer in \mathbb{N}.
Integers
Add the negative whole numbers and you get the integers, written \mathbb{Z} (from the German Zahlen, "numbers"):
Now subtraction always works: 3 − 5 = −2. Integers describe
differences and directions: a temperature below zero, an account
balance in the red, a clock that is 2 hours behind UTC. In Python
they are the
int type, which has no upper limit. Most other languages store integers in
a fixed number of bits, which is why they can
overflow.
Rational numbers
Dividing integers does not always give an integer: 1 / 3 is not a whole number. A rational number is any number that can be written as a fraction of two integers, with a denominator that is not zero:
Read this as "the set of all p over q, where p and q are integers and q is not zero". The vertical bar means "such that", and \in means "is an element of". The letter \mathbb{Q} comes from "quotient", the result of a division.
Every integer is rational too: 7 = 7/1. Written as a decimal, a rational number either stops (1/8 = 0.125) or repeats the same block of digits forever (1/3 = 0.333…, 2/7 = 0.285714285714…). That is how you can recognize one.
Irrational numbers
Some numbers cannot be written as a fraction at all. An irrational number has a decimal expansion that never stops and never repeats. The best-known examples:
- \sqrt{2} = 1.41421356\dots, the length of the diagonal of a square with sides of 1.
- \pi = 3.14159265\dots, the circumference of a circle divided by its diameter.
- e = 2.71828182\dots, the base of the natural logarithm, which appears wherever things grow continuously.
A computer can never store an irrational number exactly; it always stores an approximation with a fixed number of digits. For everyday IT work that is fine, as long as you know it is happening.
Real numbers
The rational and irrational numbers together form the real numbers, \mathbb{R}: every point on the number line. Measurements such as a response time of 0.0025 seconds, a power of 3.5 kW or an angle of 45 degrees are real numbers.
Each set contains the previous one. Mathematicians write this with the subset symbol \subset:
Beyond the real numbers
Even the real numbers have a gap: no real number squared gives −1.
The complex numbers,
\mathbb{C}, fill it with a new number
i for which
i^2 = -1. They matter in signal processing
and physics, but you will rarely need them in IT. You may meet them
by accident in Python, which has a built-in
complex type: (-8) ** (1 / 3) returns a
complex number instead of −2 (see the
roots section).
Number sets in Python
Python has a type for each practical need: int for
integers, Fraction for exact rational numbers, and
float for real numbers, stored as close approximations:
from fractions import Fraction
print(type(7), type(7.0), type(Fraction(7, 2)).__name__)
print(Fraction(1, 3) + Fraction(1, 6))
print(1 / 3 + 1 / 6)
print(Fraction(0.5), Fraction("0.1"))
<class 'int'> <class 'float'> Fraction 1/2 0.5 1/2 1/10
The fraction 1/3 + 1/6 is exactly 1/2. The float version also prints 0.5 here, but only because the small rounding errors happen to cancel out; the floating-point page shows cases where they do not.
Order of operations
What is 2 + 3 × 4? If you work from left to right you get 20; the accepted answer is 14. The order of operations is the agreement that decides which part of a calculation is done first, so that everyone gets the same answer:
- Brackets: whatever is inside them first.
- Powers (and roots), such as 4^2.
- Multiplication and division, with equal priority, from left to right.
- Addition and subtraction, with equal priority, from left to right.
Worked example: 2 + 3 \cdot 4^2.
- No brackets, so start with the power: 4^2 = 16.
- Then the multiplication: 3 \cdot 16 = 48.
- Then the addition: 2 + 48 = 50.
With brackets, (2 + 3) \cdot 4^2 is 5 \cdot 16 = 80. Two more rules catch many people out:
- A minus sign in front binds weaker than a power. -2^2 means -(2^2) = -4.
- Stacked powers are worked out from the top down. 2^{3^2} means 2^9 = 512, not 8^2 = 64.
Python follows the same rules:
print(2 + 3 * 4 ** 2)
print((2 + 3) * 4 ** 2)
print(-2 ** 2)
print((-2) ** 2)
print(2 ** 3 ** 2)
print(12 / 4 * 3)
50 80 -4 4 512 9.0
The last line shows "left to right" for operations of equal
priority: 12 / 4 is 3, then 3 × 3 is 9. Python's full list of
priorities, including comparison and logical operators, is on the
operator precedence
section of the Python pages, with the right-to-left rule for
** under
associativity.
Fractions
A fraction \frac{a}{b} means "a divided by b". The top number is the numerator, the bottom one the denominator. Fractions turn up in IT as ratios (a 16:9 screen), shares (3 of 4 servers healthy) and exact amounts that decimals cannot write (one third of a budget).
Simplifying
Dividing the top and bottom by the same number does not change a fraction's value. To simplify 18/24:
- Find a number that divides both: 6 divides 18 and 24.
- Divide both: 18 / 6 = 3 and 24 / 6 = 4.
- So \frac{18}{24} = \frac{3}{4}.
The largest number that divides both is their greatest common divisor; dividing by it gives the simplest form in one step.
Adding and subtracting
You can only add fractions with the same denominator: one third plus one sixth are pieces of different sizes. Worked example, \frac{1}{3} + \frac{1}{6}:
- Find a common denominator: 6 is a multiple of both 3 and 6.
- Rewrite 1/3 with denominator 6 by multiplying top and bottom by 2: \frac{1}{3} = \frac{2}{6}.
- Add the numerators and keep the denominator: \frac{2}{6} + \frac{1}{6} = \frac{3}{6}.
- Simplify: \frac{3}{6} = \frac{1}{2}.
Multiplying and dividing
Multiplying is easier: multiply the tops and multiply the bottoms. Dividing by a fraction is multiplying by its upside-down version:
from fractions import Fraction
print(Fraction(3, 4) * Fraction(2, 5))
print(Fraction(3, 4) / Fraction(2, 5))
print(Fraction(18, 24))
print(1 / 8, 1 / 3, 2 / 7)
3/10 15/8 3/4 0.125 0.3333333333333333 0.2857142857142857
Fraction always simplifies its result. The last line
shows the decimal forms: 1/8 stops, while 1/3 and 2/7 would repeat
forever, so the float is cut off after about 16 significant digits.
Percentages
"Per cent" means "per hundred": 21% is \frac{21}{100} = 0.21. To calculate with a percentage, first turn it into a decimal by dividing by 100.
A percentage of an amount
The standard rate of Dutch VAT (value added tax) is 21%. On an amount of €80.00 before tax:
- 21% = 0.21.
- The tax: 0.21 × 80.00 = €16.80.
- The total: 80.00 + 16.80 = €96.80, which is the same as 1.21 × 80.00 in one step.
- Back from the total to the amount before tax: divide by 1.21, not subtract 21%. 96.80 / 1.21 = €80.00, while 96.80 minus 21% of 96.80 would give €76.47.
The UK standard rate is 20%. The United States has no national VAT; the sales tax set by states and local governments is usually added at the checkout, and the same "divide, don't subtract" rule applies when you take tax out of a receipt total.
Percentage change
Kestrel Energy raises its tariff from €0.28 to €0.31 per kWh. The relative change is the difference divided by the old value:
A rise of 10% followed by a fall of 10% does not bring you back to the start: 100 becomes 110, and 10% of 110 is 11, so you end at 99. The second percentage is taken of a different, larger amount.
Percentage points
When the thing that changes is itself a percentage, there are two ways to describe the change, and they are easy to confuse. Kestrel's portal availability goes from 99.5% to 99.9%:
- The difference is 0.4 percentage points: 99.9 − 99.5 = 0.4. Percentage points are the plain difference between two percentages.
- The relative change is 0.4 / 99.5 ≈ 0.4% of the old value, which sounds much smaller and says something else.
For availability, the downtime is the better measure. 99.5% leaves 0.5% downtime and 99.9% leaves 0.1%: five times less, even though the percentages look almost the same.
old, new = 0.28, 0.31 # euros per kWh
change = (new - old) / old
print(f"Tariff change: {change:.1%}")
print(f"Difference: {(new - old) * 100:.0f} cents per kWh")
availability_before, availability_after = 0.995, 0.999
print(f"{(availability_after - availability_before) * 100:.1f} percentage points")
Tariff change: 10.7% Difference: 3 cents per kWh 0.4 percentage points
The format :.1% multiplies by 100, rounds to one
decimal and adds the percent sign. The same idea turns availability
targets into minutes of allowed downtime in a 30-day month, a common
question when reading a
service level agreement:
# Downtime allowed by an availability target, per 30-day month
minutes = 30 * 24 * 60
for target in (0.99, 0.999, 0.9999):
print(f"{target:.2%}: {(1 - target) * minutes:.1f} minutes")
99.00%: 432.0 minutes 99.90%: 43.2 minutes 99.99%: 4.3 minutes
Rounding
Rounding replaces a number by a nearby, shorter one. To round to two decimals, look at the third decimal: below 5, keep the second decimal; 5 or more, raise it by one. So 3.14159 becomes 3.14 and 2.678 becomes 2.68.
The trouble with halves
The rule "5 or more goes up" is what most people learn at school
(round half up). It has a small bias: across many numbers, it rounds
up slightly more often than down. Python's round()
therefore uses another rule, which also is the default in the
floating-point standard: a value exactly halfway goes to the nearest
even digit. This is called round half to even, or banker's
rounding, and it is explained in detail on the
floating-point page.
from decimal import Decimal, ROUND_HALF_UP
print(round(0.5), round(1.5), round(2.5), round(3.5))
print(round(2.675, 2))
print(Decimal("2.675").quantize(Decimal("0.01"), rounding=ROUND_HALF_UP))
print(Decimal("2.665").quantize(Decimal("0.01")))
0 2 2 4 2.67 2.68 2.66
- Line 1: 0.5, 1.5, 2.5 and 3.5 are exactly halfway, so they go to the even neighbor: 0, 2, 2 and 4.
- Line 2: 2.675 should round to 2.68 by either rule, yet Python gives 2.67. The float 2.675 is stored as a binary approximation that is slightly less than 2.675, so it is not a half at all (see floating point).
-
Line 3:
Decimalstores decimal digits exactly, andROUND_HALF_UPgives the school answer, 2.68. -
Line 4: without a rounding argument,
Decimalalso rounds halves to even: 2.665 becomes 2.66.
For invoices, use Decimal with the rounding rule your
finance department prescribes, and round once, at the end. Other
languages have the same tool, such as
C#'s decimal.
Truncating and rounding down
Cutting off the decimals (truncating) is not the same as rounding down. For positive numbers they agree; for negative numbers they do not, because "down" means toward minus infinity:
import math
print(int(2.9), int(-2.9))
print(math.floor(-2.9), math.ceil(-2.1))
2 -2 -3 -2
int() truncates toward zero, so −2.9 becomes −2;
math.floor rounds down, so −2.9 becomes −3. The
floor and ceiling
functions are introduced on the first page of this topic.
Scientific notation
Very large and very small numbers are hard to read: is 0.0000025 two and a half millionths or two and a half ten-millionths? Scientific notation writes every number as a number between 1 and 10 (the coefficient) times a power of 10:
Worked example: write 0.0025 seconds (a 2.5 ms latency) this way.
- Move the decimal point until exactly one non-zero digit is in front of it: 0.0025 becomes 2.5.
- Count the moves: three places to the right.
- Moving right makes the number bigger, so compensate with a negative power: 2.5 \times 10^{-3}. (10^{-3} is 1/1000; see negative exponents.)
For a large number, the point moves left and the power is positive:
the 11 in 1.496 \times 10^{11} says the
point moved 11 places. Programming languages write the "times 10 to
the power" part as e: 1.496e+11 and
2.5e-03.
distance_m = 149_600_000_000 # Earth to Sun, in meters
latency_s = 0.0025 # 2.5 milliseconds
print(f"{distance_m:.3e}")
print(f"{latency_s:.1e}")
print(6.022e23 * 2)
print(1e-3 == 0.001)
1.496e+11 2.5e-03 1.2044e+24 True
The underscores in 149_600_000_000 are only for
readability; Python ignores them. The format :.3e
shows three digits after the point in scientific notation.
The number of meaningful digits in a value is its number of
significant figures: 1.496 × 1011 has
four. A Python float holds about 15 to 17 significant decimal
digits, which is why 1/3 printed as
0.3333333333333333 above. Metric prefixes are the
everyday version of the same idea: 2.5 × 10−3 seconds is
2.5 milliseconds. The
units and prefixes page
covers them, including the binary prefixes used for memory.