Equations and Inequalities
Solving linear and quadratic equations, the quadratic formula, exponential and logarithmic equations, systems of two equations, and inequalities with intervals.
What an equation is
An equation is a statement that two expressions are equal, such as 0.27x + 6.50 = 60.50. The letter x is the unknown. A solution is a value for the unknown that makes the statement true. Solving means finding all solutions, or showing that there are none.
Think of an equation as a balance scale. Both sides weigh the same. You may add, subtract, multiply or divide, as long as you do exactly the same to both sides: then the scale stays in balance and the solutions stay the same. The only operations that are not allowed are multiplying by zero (that makes every equation true) and dividing by zero (that is not defined).
The expressions themselves are the subject of Algebra: Expressions and Factoring; this page uses expanding and factoring from there.
Linear equations
A linear equation is an equation in which the unknown appears only to the first power: no x^2, no \sqrt{x}, no x in a denominator. Every linear equation in one unknown can be written as ax + b = 0 with a \ne 0, and then it has exactly one solution: x = -b/a. Its graph is a straight line, which is where the name comes from.
Worked example: from a bill to kWh
Kestrel Energy charges €0.27 per kWh plus a fixed fee of €6.50 a month. A customer's bill is €60.50. How many kWh did they use? Call the usage x.
- Write the equation: 0.27x + 6.50 = 60.50.
- Subtract 6.50 from both sides to remove the fixed fee: 0.27x = 54.
- Divide both sides by 0.27: x = 54 / 0.27 = 200.
- Check in the original equation: 0.27 × 200 + 6.50 = 54 + 6.50 = 60.50. Correct.
The order of the steps is the reverse of how the bill was built: the bill multiplies first and adds the fee last, so solving removes the fee first and undoes the multiplication last.
from fractions import Fraction
# Monthly bill = 0.27 * kWh + 6.50; the bill was 60.50. How many kWh?
rate, fixed, bill = Fraction("0.27"), Fraction("6.50"), Fraction("60.50")
kwh = (bill - fixed) / rate
print(kwh)
200
Fraction calculates with exact fractions, so the answer
is exactly 200 and not a float such as 199.99999999999997.
The line through two points
A straight line has the equation y = mx + c: the slope m is how much y changes when x goes up by 1, and c is where the line crosses the y-axis. Given two points (x_1, y_1) and (x_2, y_2), the slope is
that is, the change in y divided by the change in x. A smart meter reads 1,310 kWh on day 10 and 1,640 kWh on day 40:
- Slope: m = (1640 - 1310)/(40 - 10) = 330/30 = 11 kWh per day.
- Put one point into y = 11x + c: 1310 = 11 \cdot 10 + c, so c = 1310 - 110 = 1200.
- The line is y = 11x + 1200. Check with the other point: 11 × 40 + 1200 = 1,640. Correct.
Lines as functions, and using them to forecast, are on Functions and Graphs.
Equations with the unknown in a fraction
When the unknown sits in a denominator, multiply both sides by that denominator to get rid of the fraction. That is allowed only where the denominator is not zero, so note the forbidden values first.
- Solve \frac{x}{x - 2} = 3. Forbidden: x = 2 (the denominator would be 0).
- Multiply both sides by x - 2: x = 3(x - 2) = 3x - 6.
- Subtract 3x from both sides: -2x = -6, so x = 3.
- 3 is not the forbidden value, and 3 / (3 − 2) = 3. Correct.
Compare \frac{x}{x - 2} = \frac{2}{x - 2}. Multiplying by x - 2 gives x = 2, which is exactly the forbidden value. This equation has no solution: the step created a false one.
Quadratic equations
A quadratic equation is an equation that can be written as
The highest power of the unknown is 2. Its graph is a parabola, and the solutions are the points where the parabola crosses the x-axis: there can be two, one or none. Before solving, always move everything to one side so that the other side is 0.
Solving by factoring
If a product is zero, at least one of its factors is zero. So if you can factor the left side, each factor gives a solution.
- Solve x^2 - 5x + 6 = 0. Look for two numbers that multiply to 6 and add up to −5: −2 and −3.
- Factor: (x - 2)(x - 3) = 0.
- Either x - 2 = 0 or x - 3 = 0, so x = 2 or x = 3.
Without a bx term it is even simpler: x^2 = 49 gives x = 7 or x = -7, written x = \pm 7, because both 7^2 and (-7)^2 are 49.
The quadratic formula
Factoring by eye only works for friendly numbers. The quadratic formula always works:
Read it as: "minus b, plus or minus the square root of b squared minus four a c, all divided by two a". The plus-or-minus gives the two solutions. Worked example, 2x^2 + 3x - 2 = 0:
- Read off the coefficients: a = 2, b = 3, c = -2 (the sign belongs to the number).
- Compute the part under the root: 3^2 - 4 \cdot 2 \cdot (-2) = 9 + 16 = 25, and \sqrt{25} = 5.
- Plus: x = (-3 + 5)/4 = 2/4 = 0.5. Minus: x = (-3 - 5)/4 = -8/4 = -2.
- Check x = -2: 2 × 4 + 3 × (−2) − 2 = 8 − 6 − 2 = 0. Correct.
The discriminant: how many solutions?
The discriminant is the part under the square root, D = b^2 - 4ac. Its sign tells you how many real solutions there are before you calculate them:
- D \gt 0: two solutions (the parabola crosses the x-axis twice);
- D = 0: one solution, x = -b/(2a) (the parabola touches the axis);
- D \lt 0: no real solutions, because no real number squared is negative (the parabola stays above or below the axis).
A function that applies the formula and the three cases, with one example of each:
import math
def solve_quadratic(a, b, c):
d = b * b - 4 * a * c # discriminant
if d < 0:
return []
if d == 0:
return [-b / (2 * a)]
r = math.sqrt(d)
return sorted([(-b - r) / (2 * a), (-b + r) / (2 * a)])
print(solve_quadratic(1, -5, 6))
print(solve_quadratic(1, -4, 4))
print(solve_quadratic(1, 0, 1))
print(solve_quadratic(2, 3, -2))
[2.0, 3.0] [2.0] [] [-2.0, 0.5]
Worked example: a full mesh
In a full-mesh network every device has a direct link to every other device. With n devices that takes n(n - 1)/2 links: each of the n devices links to the n - 1 others, and every link is counted twice. A design has a budget of 45 links. How many devices can be fully meshed?
- Equation: n(n - 1)/2 = 45. Multiply both sides by 2: n^2 - n = 90.
- Move everything to one side: n^2 - n - 90 = 0, so a = 1, b = -1, c = -90.
- Discriminant: (-1)^2 - 4 \cdot 1 \cdot (-90) = 1 + 360 = 361 = 19^2.
- Solutions: n = (1 + 19)/2 = 10 or n = (1 - 19)/2 = -9.
- A number of devices cannot be negative, so the answer is 10.
import math
# A full mesh of n devices needs n(n - 1)/2 links. Which n gives 45 links?
# n(n - 1)/2 = 45 -> n^2 - n - 90 = 0 -> a = 1, b = -1, c = -90
a, b, c = 1, -1, -90
d = b * b - 4 * a * c
print("discriminant:", d, "root:", math.isqrt(d))
print("solutions:", (-b + math.isqrt(d)) // (2 * a), (-b - math.isqrt(d)) // (2 * a))
n = 10
print(n * (n - 1) // 2, "links")
discriminant: 361 root: 19 solutions: 10 -9 45 links
Deciding which mathematical solutions make sense in the real situation is part of the job: the equation does not know that devices come in whole, positive numbers.
Higher-degree equations
For powers above 2 there is no simple general formula to remember, but two tricks solve most textbook cases.
- Factor out the unknown. x^3 - 4x = 0 becomes x(x^2 - 4) = 0, and then x(x - 2)(x + 2) = 0: the solutions are 0, 2 and −2.
- Substitute. In x^4 - 5x^2 + 4 = 0, write u = x^2. That gives the quadratic u^2 - 5u + 4 = 0, so u = 1 or u = 4. Back to x: x^2 = 1 or x^2 = 4, so x = \pm 1 or x = \pm 2: four solutions.
In practice, software finds the solutions of higher-degree equations numerically, by trying values and narrowing down, much like a binary search.
Radical, exponential, logarithmic and trigonometric equations
Equations with a square root
To remove a square root, isolate it on one side and square both sides. Squaring is not reversible, though: both 3 and −3 square to 9. So squaring can add solutions that do not fit the original equation, called extraneous solutions. Checking is not optional here.
- Solve \sqrt{2x + 3} = x. The root is already alone on the left.
- Square both sides: 2x + 3 = x^2.
- Move everything to one side: x^2 - 2x - 3 = 0, which factors as (x - 3)(x + 1) = 0: candidates x = 3 and x = -1.
- Check x = 3: \sqrt{9} = 3. Correct.
- Check x = -1: \sqrt{1} = 1, but the right side is −1. Not equal, so −1 is extraneous. The only solution is 3.
import math
# sqrt(2x + 3) = x: squaring gives x^2 - 2x - 3 = 0, so x = 3 or x = -1
for x in (3, -1):
left, right = math.sqrt(2 * x + 3), x
print(x, left, right, "solution" if left == right else "extraneous")
3 3.0 3 solution -1 1.0 -1 extraneous
Exponential equations
In an exponential equation the unknown is in the exponent, as in 2^x = 64. If you can write both sides as powers of the same base, compare exponents: 64 = 2^6, so x = 6. Otherwise take the logarithm of both sides, which brings the exponent down (the rules are on Powers, Roots and Logarithms).
Worked example: Kestrel's meter-data archive holds 40 TB and grows 30% per year. When does it pass 200 TB? After n years it holds 40 \cdot 1.3^n TB.
- Equation: 40 \cdot 1.3^n = 200.
- Divide both sides by 40: 1.3^n = 5.
- Take the natural logarithm of both sides and use \ln(a^n) = n \ln a: n \ln 1.3 = \ln 5.
- Divide: n = \ln 5 / \ln 1.3 \approx 1.609 / 0.262 \approx 6.13.
- Growth is counted per year here, so the archive passes 200 TB during the seventh year.
import math
# Archive of 40 TB grows 30% per year. When does it pass 200 TB?
years = math.log(200 / 40) / math.log(1.3)
print(f"{years:.2f} years -> after {math.ceil(years)} full years")
print(f"{40 * 1.3 ** 6:.1f} TB after 6 years, {40 * 1.3 ** 7:.1f} TB after 7")
6.13 years -> after 7 full years 193.1 TB after 6 years, 251.0 TB after 7
Any base works for this step, as long as you use the same one on
both sides:
\log_{10} 5 / \log_{10} 1.3 gives the same
6.13. In Python, math.log(x) is the natural logarithm.
Logarithmic equations
A logarithmic equation has the unknown inside a logarithm. Undo the logarithm by raising its base to both sides, since \log_b y = k means exactly y = b^k.
- \log_2 x = 10 gives x = 2^{10} = 1024.
- \log_{10}(x - 1) = 2 gives x - 1 = 10^2 = 100, so x = 101. A logarithm only exists for positive numbers, so check that x - 1 \gt 0: it is.
A typical IT question turns into a logarithm with an inequality: how many bits do you need to give 5,000 customers each a different number? With n bits there are 2^n patterns, so you need 2^n \ge 5000, that is n \ge \log_2 5000 \approx 12.29. Bits come whole, so round up: 13 bits.
import math
# Smallest number of bits n with 2^n >= customers
for customers in (200, 5000, 65536, 65537):
n = math.ceil(math.log2(customers))
print(f"{customers:>6} customers -> {n} bits (2^{n} = {2 ** n:,})")
200 customers -> 8 bits (2^8 = 256) 5000 customers -> 13 bits (2^13 = 8,192) 65536 customers -> 16 bits (2^16 = 65,536) 65537 customers -> 17 bits (2^17 = 131,072)
Trigonometric equations
Sine and cosine repeat every 360° (every 2\pi radians), so a trigonometric equation usually has infinitely many solutions. Solve \sin x = \tfrac{1}{2}:
- The inverse sine gives one solution: x = 30^\circ.
- Sine is also \tfrac{1}{2} at 180^\circ - 30^\circ = 150^\circ, because \sin(180^\circ - x) = \sin x.
- Add any whole number of full turns: all solutions are 30^\circ + k \cdot 360^\circ and 150^\circ + k \cdot 360^\circ for any integer k.
import math
# sin x = 1/2 for x in [0, 360) degrees: the calculator gives one solution
first = math.degrees(math.asin(0.5))
print(first)
second = 180 - first # sin(180 - x) = sin x
print(round(first, 9), round(second, 9))
for x in (30, 150, 390, 510):
print(x, round(math.sin(math.radians(x)), 9))
30.000000000000004 30.0 150.0 30 0.5 150 0.5 390 0.5 510 0.5
math.asin returns only one angle, and in radians; the
first line also shows a tiny floating-point error. Sine, cosine and
the unit circle are explained on
Geometry and Trigonometry.
Systems of two equations
Two unknowns need two equations. A system of equations asks for values that make both true at the same time. There are two standard methods: substitution (solve one equation for one unknown and put that into the other) and elimination (add or subtract the equations so that one unknown drops out).
Worked example: when is plan B cheaper?
Kestrel offers two tariff plans. Plan A costs €0.30 per kWh plus €5 a month; plan B costs €0.25 per kWh plus €12 a month. With x kWh and monthly cost y:
- Both right sides equal y, so they equal each other: 0.30x + 5 = 0.25x + 12.
- Subtract 0.25x and 5 from both sides: 0.05x = 7.
- Divide by 0.05: x = 140.
- Put it back into either equation: y = 0.30 \cdot 140 + 5 = 47. Check with the other: 0.25 × 140 + 12 = 47. Correct.
At 140 kWh a month both plans cost €47: the break-even point. Below it plan A is cheaper, above it plan B, because plan B's line is less steep.
from fractions import Fraction
# Plan A: 0.30 per kWh + 5 a month. Plan B: 0.25 per kWh + 12 a month.
# 0.30x + 5 = 0.25x + 12 -> 0.05x = 7
x = (Fraction(12) - 5) / (Fraction("0.30") - Fraction("0.25"))
print(x, "kWh")
for kwh in (100, 140, 200):
a, b = 0.30 * kwh + 5, 0.25 * kwh + 12
print(kwh, f"A={a:.2f}", f"B={b:.2f}")
140 kWh 100 A=35.00 B=37.00 140 A=47.00 B=47.00 200 A=65.00 B=62.00
The same calculation decides between cloud pricing models, such as paying per hour or committing for a year; see Comparing the models for one server.
Worked example: substitution
Kestrel runs 10 virtual machines. Small ones have 4 GB of memory, large ones 16 GB, and together they use 88 GB. How many of each? Call the number of small machines x and of large ones y.
- Count: x + y = 10. Memory: 4x + 16y = 88.
- Solve the first for x: x = 10 - y.
- Substitute into the second: 4(10 - y) + 16y = 88, so 40 - 4y + 16y = 88, so 12y = 48, so y = 4.
- Then x = 10 - 4 = 6. Check: 6 × 4 + 4 × 16 = 24 + 64 = 88. Correct.
from fractions import Fraction
# x + y = 10 virtual machines; 4x + 16y = 88 GB of memory
# Substitute x = 10 - y: 4(10 - y) + 16y = 88 -> 12y = 48
y = Fraction(88 - 4 * 10, 16 - 4)
x = 10 - y
print("small:", x, "large:", y)
print("check:", x + y, "VMs,", 4 * x + 16 * y, "GB")
small: 6 large: 4 check: 10 VMs, 88 GB
A system can also have no solution (two parallel lines, such as two plans with the same price per kWh but different fees) or infinitely many (two equations that say the same thing, such as x + y = 10 and 2x + 2y = 20).
Inequalities
An inequality compares two expressions with \lt (less than), \le (less than or equal to), \gt or \ge, instead of =. Its solution is usually not one number but a whole range of numbers.
Solving an inequality
You solve an inequality exactly like an equation, with one extra rule: multiplying or dividing both sides by a negative number flips the inequality sign. That is because multiplying by −1 mirrors the number line: 2 < 5, but −2 > −5.
- Solve 2x - 3 \lt 7. Add 3 to both sides: 2x \lt 10. Divide by 2 (positive, no flip): x \lt 5.
- Solve -3x + 6 \ge 0. Subtract 6: -3x \ge -6. Divide by −3 and flip: x \le 2.
- Check the second with a value on each side: at x = 0, 6 ≥ 0 is true; at x = 3, −3 ≥ 0 is false. So the solutions are indeed the numbers up to 2.
Worked example: an SLO downtime budget
Kestrel's customer portal has a service level objective of at least 99.9% availability per 30-day month. How much downtime d (in minutes) is allowed? (What an SLO is, and how it differs from an SLA, is on SLA, SLO and SLI; Kestrel's contract SLA, the one with consequences, is a looser 99.5%, see SLOs versus SLAs.)
- A 30-day month has 30 × 24 × 60 = 43,200 minutes.
- Availability is uptime divided by total time: \frac{43200 - d}{43200} \ge 0.999.
- Multiply both sides by 43,200 (positive, no flip): 43200 - d \ge 43156.8.
- Subtract 43,200: -d \ge -43.2. Multiply by −1 and flip: d \le 43.2.
At most 43.2 minutes of downtime per month. One minute more misses the objective:
# SLO: availability of at least 99.9% in a 30-day month
minutes = 30 * 24 * 60
budget = minutes * (1 - 0.999)
print(minutes, "minutes in the month")
print(f"allowed downtime: at most {budget:.1f} minutes")
for down in (20, 43, 44, 95):
availability = (minutes - down) / minutes
print(f"{down:>3} min down -> {availability:.4%}", availability >= 0.999)
43200 minutes in the month allowed downtime: at most 43.2 minutes 20 min down -> 99.9537% True 43 min down -> 99.9005% True 44 min down -> 99.8981% False 95 min down -> 99.7801% False
Intervals
The solutions of an inequality usually form an interval: all real numbers between two endpoints, where each endpoint is either included or not. A square bracket means "included", a round bracket "not included".
| Interval | Inequality | Endpoints |
|---|---|---|
| [a, b] | a \le x \le b | both included (closed) |
| (a, b) | a \lt x \lt b | neither included (open) |
| [a, b) | a \le x \lt b | a included, b not (half-open) |
| (-\infty, b) | x \lt b | no lower end; infinity is never included |
| [a, \infty) | x \ge a | a included, no upper end |
Half-open intervals are everywhere in programming. Python's
range(0, 10) covers [0, 10): 0 is included, 10 is not.
A chained comparison such as -1 <= x < 3 tests
membership of [−1, 3) directly (see
chained comparisons), and an input check such as
0 <= port <= 65535 tests the closed interval [0,
65535] of valid TCP and UDP port numbers.
# 2x - 3 < 7 -> x < 5
print([x for x in range(0, 10) if 2 * x - 3 < 7])
# -1 <= x < 3 as a chained comparison: the interval [-1, 3)
print([x for x in range(-3, 5) if -1 <= x < 3])
[0, 1, 2, 3, 4] [-1, 0, 1, 2]
Quadratic inequalities
For an inequality such as x^2 - 5x + 6 \lt 0, first solve the equation x^2 - 5x + 6 = 0: the roots are 2 and 3. The parabola opens upward (the coefficient of x^2 is positive), so it is below zero exactly between its roots. The solution is the open interval (2, 3); with \le it would be the closed interval [2, 3].
# x^2 - 5x + 6 < 0: the parabola is below zero between its roots 2 and 3
xs = [x / 2 for x in range(0, 11)]
print([x for x in xs if x * x - 5 * x + 6 < 0])
print([x for x in xs if x * x - 5 * x + 6 <= 0])
[2.5] [2.0, 2.5, 3.0]
Where equations show up in IT
- Break-even points: when does a reserved instance, a bigger license tier or a different tariff become cheaper? Two linear cost lines and one system of equations.
- Capacity planning: when does storage, bandwidth or a license count run out? Usually an exponential equation solved with a logarithm.
- Budgets and thresholds: SLA downtime budgets, alert thresholds and error budgets are inequalities.
- Validation: range checks on input (ports, VLAN IDs 1–4094, percentages 0–100) are intervals.
- Sizing: how many bits, address bits or hash bits you need is an inequality with powers of 2; the subnetting formulas on The formulas are the best-known example.