Logarithms sound intimidating, but you already understand them. You’ll prove it to yourself in the next few minutes — starting with nothing more than counting zeros.
Look at these numbers:
| Number | How many zeros? |
|---|---|
| 10 | ?1 |
| 100 | ?2 |
| 1000 | ?3 |
| 10000 | ?4 |
| 100000 | ?5 |
How many zeros does 10 have?
That’s right! 10 has 1 zero.
How many zeros does 100 have?
That’s right! 100 has 2 zeros.
How many zeros does 1000 have?
That’s right! 1000 has 3 zeros.
How many zeros does 10000 have?
That’s right! 10000 has 4 zeros. Do you see the pattern? Each time we add a zero, the count goes up by 1.
How many zeros does 100000 have?
That’s right! 100000 has 5 zeros.
This “number of zeros” has a fancy name: logarithm.
In Python, ** is the power operator. 10**2 means
“10 to the power of 2”.
What does each expression give?
| Expression | Value | Log |
|---|---|---|
10**1 | ?10 | 1 |
10**2 | ?100 | 2 |
10**3 | ?1000 | 3 |
10**4 | ?10000 | 4 |
The power tells you how many zeros. The number of zeros tells you the power.
Going from power → number is exponentiation.
Going from number → power is logarithm.
So when someone asks “what is log(1000)?” they are really asking:
10 to the power of what gives 1000?
What is the answer?
That’s right! The answer is 3, because 10 × 10 × 10 = 103 = 1000.
That’s it. That’s all a logarithm is. Let’s fill out a few more:
| Question | In other words | Answer |
|---|---|---|
| log(10) | 10? = 10 | ?1 |
| log(100) | 10? = 100 | ?2 |
| log(1000) | 10? = 1000 | ?3 |
| log(10000) | 10? = 10000 | ?4 |
Quick fire: answer without Python.
10 to the power of what gives 100?
Think: how many times do you multiply 10 by itself to get 100?
That’s right! 2, because 10 × 10 = 102 = 100.
10 to the power of what gives 10?
That’s right! 1, because 101 is just 10 itself.
10 to the power of what gives 1?
(Tricky! What happens when the power is 0?)
That’s right! 0, because any number to the power of 0 is 1. Try 10**0 in Python — you get 1.
So what is log(1)?
That’s right! log(1) = 0.
Key fact: log of 1 is always 0, no matter what the base is. Because anything0 = 1.
What is log(500)?
In other words: 10 to the power of what gives 500?
It’s not a whole number. But you already know something about it.
| 102 = 100 | → too small |
| 103 = 1000 | → too big |
So log(500) is between 2 and 3.
Just take a guess!
You know log(500) is between 2 and 3. Let’s find it by guessing, checking, and narrowing down.
Check your guess: compute 10**x in Python.
guess = 2.5
print(10**guess)
316.23
316 is less than 500. Your guess was too low!
So the answer is between …2.5 and 3. Take a better guess!
guess = 2.75
print(10**guess)
562.34
Too high! Answer is between 2.5 and 2.75.
guess = 2.625
print(10**guess)
421.70
Too low! Answer is between 2.625 and 2.75.
Your next guess will be between which two numbers?
Call them lo and hi, then calculate
the midpoint:
lo = 2
hi = 3
guess = (lo + hi) / 2
print(f"Guess: {guess}")
print(f"10**guess: {10**guess}")
Guess: 2.5
10**guess: 316.23
Too low! So the answer is between guess and hi.
Update lo = guess and compute
a new midpoint.
Can you automatically change lo or hi
depending on whether 10**guess was higher or lower than 500?
This is Python’s if-else:
lo = 2
hi = 3
value = 500
guess = (lo + hi) / 2
if 10**guess > value:
# guess was too high — which bound should change?
else:
# guess was too low — which bound should change?
print(f"New range: [{lo}, {hi}]")
lo = 2
hi = 3
value = 500
guess = (lo + hi) / 2
if 10**guess > value:
hi = guess # too high — bring ceiling down
else:
lo = guess # too low — raise the floor
print(f"New range: [{lo}, {hi}]")
New range: [2.5, 3]
Run the logic again and again. Do you see your guess getting closer?
| Round | lo | hi | guess | 10guess | |
|---|---|---|---|---|---|
| 1 | 2 | 3 | 2.5 | 316.23 | ↑ too low |
| 2 | 2.5 | 3 | 2.75 | 562.34 | ↓ too high |
| 3 | 2.5 | 2.75 | 2.625 | 421.70 | ↑ too low |
| 4 | 2.625 | 2.75 | 2.6875 | 486.97 | ↑ too low |
| 5 | 2.6875 | 2.75 | 2.7188 | 523.30 | ↓ too high |
Each guess cuts the range in half. After 20 rounds, the range is smaller than 0.000001.
But copying this block 20 times is tedious…
Copying and pasting the same block over and over is tedious.
Python’s for loop can repeat it for you —
as many times as you want.
Run your logic 20 times using a for loop:
lo = 2
hi = 3
value = 500
for i in range(20):
guess = (lo + hi) / 2
# Use your if-else logic here to update lo or hi
print(f"log({value}) = {guess}")
print(f"10**guess = {10**guess}")
lo = 2
hi = 3
value = 500
for i in range(20):
guess = (lo + hi) / 2
if 10**guess > value:
hi = guess
else:
lo = guess
print(f"log({value}) = {guess}")
print(f"10**guess = {10**guess}")
log(500) = 2.6989700043360187
10**guess = 500.0000000000002
Did 10**guess get close to 500?
Let’s compare with Python’s built-in:
import math
print(f"Your answer: {guess}")
print(f"math.log10(500): {math.log10(500)}")
Your answer: 2.6989700043360187
math.log10(500): 2.6989700043360187
They match! Your simple guessing loop matches the built-in function.
Now repeat the process for log(50).
What should lo and
hi start at?
101 = 10 (too small),
102 = 100 (too big) →
lo = 1, hi = 2
Run the same loop — it works!
Every guess eliminates half of the remaining range.
| After 1 guess: | half the range left |
| After 5 guesses: | 1/32 of the range |
| After 10 guesses: | 1/1024 of the range |
| After 20 guesses: | 1/1,048,576 of the range |
This “halving” strategy is called binary search — “binary” because you split the range in two each time.
It is one of the most important algorithms in computer science — and you just invented it.
You’ve found log(500) and log(50) by repeating the same code with different numbers. Copying code is messy — a function packages it so you can call it with any input.
Consolidate your for-loop and if-else into one function. Fill in the two missing lines:
def my_log(value, base, lo, hi, iterations):
for i in range(iterations):
guess = (lo + hi) / 2
if base**guess > value:
pass # Your code goes here
else:
pass # Your code goes here
return guess
def my_log(value, base, lo, hi, iterations):
for i in range(iterations):
guess = (lo + hi) / 2
if base**guess > value:
hi = guess
else:
lo = guess
return guess
When base**guess > value (too high),
bring the ceiling down: hi = guess.
When too low, raise the floor: lo = guess.
Same logic you’ve been doing all along — now packaged as a reusable function.
Test it:
import math
result = my_log(500, 10, 2, 3, 20)
print(f"my_log: {result}")
print(f"math.log10: {math.log10(500)}")
my_log: 2.6989700043360187
math.log10: 2.6989700043360187
They match!
Try more:
print(my_log(1000, 10, 2, 4, 20)) # 3.0
print(my_log(8, 2, 2, 4, 20)) # 3.0
print(my_log(6, 2, 2, 3, 20)) # 2.5849625...
print(my_log(27, 3, 2, 4, 20)) # 3.0
Your function works for any base
— because the condition base**guess > value
works no matter what the base is!
Use your function to explore a pattern:
| Number | log10 |
|---|---|
| 5 | 0.699 |
| 50 | 1.699 |
| 500 | 2.699 |
| 5000 | ?3.699 |
Every time you multiply the number by 10, the log goes up by exactly 1.
That makes sense — you’re adding one more power of 10!
Congratulations! In one sitting you learned:
| What you did | Python concept |
|---|---|
| Counted zeros (10, 100, 1000) | Expressions |
Guessed log(500), checked with 10**x | Variables |
| Tracked the range with lo and hi | Variables (lo, hi) |
| Automated the high/low decision | if / else |
| Repeated the guessing 20 times | for loop |
Packaged it as my_log | Functions (def) |
The algorithm you built — binary search — doesn’t just work for logarithms. It’s one of the most important algorithms in computer science.
And you just invented it.
Up to now, every “log” you computed was secretly log base 10 — counting zeros only works because our number system is built on powers of 10. From here on we’ll write the base explicitly, like log2 or log3, because the base is about to change.
There’s nothing special about 10. Let’s try base 2.
log2(x) asks: 2 to the power of what gives x?
| Question | log2 |
|---|---|
| 2? = 4 | ?2 |
| 2? = 8 | ?3 |
| 2? = 16 | ?4 |
| 2? = 32 | ?5 |
| 2? = 1024 | ?10 |
log2(4) = ? How many times do you multiply 2 by itself to get 4?
That’s right! 2, because 2 × 2 = 4.
log2(8) = ? One more factor of 2…
That’s right! 3, because 2 × 2 × 2 = 8.
log2(16) = ?
That’s right! 4, because 24 = 16.
log2(32) = ? Keep doubling: 16 × 2 = 32.
That’s right! 5, because 25 = 32.
log2(1024) = ? Computers love this one — 1 KB is 1024 bytes!
That’s right! 10, because 210 = 1024. You need to multiply 2 by itself 10 times to reach 1024.
It works with any base. The question is always:
base? = value
| Question | Answer |
|---|---|
| 3? = 27 | ?3 |
| 5? = 125 | ?3 |
| 4? = 64 | ?3 |
| 7? = 1 | ?0 |
log3(27) = ? Think: 3 × 3 × 3 = ?
That’s right! 3, because 3 × 3 × 3 = 27.
log5(125) = ? Think: 5 × 5 × 5 = ?
That’s right! 3, because 5 × 5 × 5 = 125.
log4(64) = ? Think: 4 × 4 × 4 = ?
That’s right! 3 again, because 43 = 64. Notice all three were 3 — but the bases and values were different!
log7(1) = ? Remember: any number to the power of 0 is…
That’s right! 0, because 70 = 1. This works for every base: anything0 = 1.
Verify in Python: math.log(27, 3) → 3.0, math.log(125, 5) → 3.0, math.log(64, 4) → 3.0
What is log2(6)?
In other words: 2 to the power of what gives 6?
You know log2(4) = 2 and log2(8) = 3. Since 6 is between 4 and 8, the answer must be between 2 and 3.
You already have a function for this!
print(my_log(6, 2, 2, 3, 20))
2.584962500721156
log2(6) ≈ 2.585.
Verify: math.log(6, 2) → 2.585. Your binary search nails it!
Python gives you math.log10() and math.log2() built in.
But what if you need log base 3? Or base 5?
Try this experiment. For each row, divide log10(value) by log10(base):
| Want | log10(value) | log10(base) | ratio | expected |
|---|---|---|---|---|
| log3(27) | 1.431 | 0.477 | ?3.0 | 3 |
| log2(8) | 0.903 | 0.301 | ?3.0 | 3 |
| log5(125) | 2.097 | 0.699 | ?3.0 | 3 |
That’s right! 1.431 ÷ 0.477 = 3.0 — exactly what we expected!
Try the next row: 0.903 ÷ 0.301 = ?
That’s right! 3.0 again. The pattern holds.
One more: 2.097 ÷ 0.699 = ?
That’s right! 3.0 every time. Dividing log10(value) by log10(base) always gives the correct log!
You just discovered the change of base formula:
logb(x) = log10(x) ÷ log10(b)
This means you can compute a log in any base using only log10:
import math
def log_base(value, base):
return math.log10(value) / math.log10(base)
Test it:
log_base(27, 3) → 3.0 ✓
log_base(6, 2) → 2.585 ✓
log_base(1024, 2) → 10.0 ✓
The base in the denominator can be anything — log2, log10, ln — the ratio is always the same.
(Python’s math.log(value, base) does exactly this internally.)
What you just invented:
| You did | Real name |
|---|---|
| Counted zeros in 10, 100, 1000… | log10 |
| “base to the power of what?” | Logarithm definition |
| Guessing and narrowing down | Binary search |
my_log(value, base, lo, hi, iters) | Binary-search log calculator |
| Dividing two log10 values | Change of base formula |
You didn’t learn logarithms. You invented them.