Kiru Lab  /  Foundations: Python as an Instrument  /  Python From First Principles

Numbers, Text, and Truth

The four basic types, how to convert between them, and the floating-point fact that will bite you in every later track.

Concept  ·  about 30 minutes

Python has a handful of basic types and you will use four of them constantly: integers, floating-point numbers, strings of text, and booleans. Knowing which one you are holding prevents most early confusion.

The four you need now
count = 12               # int   — whole number, exact
learning_rate = 0.01     # float — decimal, approximate (see below)
name = "gradient"        # str   — text, in quotes
converged = True         # bool  — True or False, capitalized

print(type(count), type(learning_rate), type(name), type(converged))

Converting between them

Python will not silently convert types for you, which is a feature. `"3" + 4` is an error rather than a guess, because the interpreter cannot know whether you meant 7 or "34". Convert explicitly and the ambiguity disappears.

Explicit conversion
int("42")        # 42     — string to integer
float("3.14")    # 3.14   — string to float
str(42)          # "42"   — number to string
int(3.9)         # 3      — truncates toward zero, does NOT round
round(3.9)       # 4      — this rounds
bool(0)          # False  — 0, "", [], and None are falsy; almost everything else is truthy

Formatting text

An f-string lets you drop values directly into text. It is the only string formatting you need to learn, and you will use it in every print statement and log line you ever write.

f-strings
loss = 0.03847
epoch = 12

print(f"epoch {epoch}: loss {loss}")            # epoch 12: loss 0.03847
print(f"epoch {epoch}: loss {loss:.3f}")        # epoch 12: loss 0.038   <- 3 decimals
print(f"epoch {epoch}: loss {loss:.2e}")        # epoch 12: loss 3.85e-02

The floating-point fact

Floats are stored in binary and most decimal fractions have no exact binary representation, exactly as one third has no exact decimal representation. So arithmetic on them accumulates tiny errors.

The result that surprises everyone once
0.1 + 0.2            # 0.30000000000000004
0.1 + 0.2 == 0.3     # False

# So never compare floats with ==. Compare with a tolerance:
abs((0.1 + 0.2) - 0.3) < 1e-9    # True
Why this matters far beyond this lesson

Every number in a neural network and every joint angle in a robot arm is a float. Accumulated floating-point error is why gradient checks compare within a tolerance rather than for equality, why GPU results are not bitwise reproducible across machines, and why an ill-conditioned matrix is dangerous. You are meeting the constraint here in its simplest form.


Hold on to

  • Python does not silently convert types — convert explicitly
  • f-strings are the only formatting syntax you need
  • Never compare floats with ==; compare within a tolerance

Work through

Try each one before opening the solution. Getting it wrong first is most of where the learning happens.

  1. Predict the output of `int("7") + int(3.9)` before running it, then run it. Explain any difference between your prediction and the result.
    Hint

    `int()` on a float does not round.

    Solution

    The answer is 10. `int("7")` is 7, and `int(3.9)` truncates toward zero to 3, not 4. Most people predict 11 because they expect rounding. Use `round()` when you mean rounding — this distinction causes real off-by-one bugs in indexing code.

    int("7") + int(3.9)      # 10
    int("7") + round(3.9)    # 11
    int(-3.9)                # -3  — truncates toward zero, not down
  2. Write a function `close_enough(a, b, tol=1e-9)` that compares two floats safely, and show that it returns True for `0.1 + 0.2` and `0.3` where `==` returns False.
    Hint

    Compare the absolute difference against a small tolerance.

    Solution

    Take the absolute difference and check it is below a tolerance. Python also ships `math.isclose`, which handles relative tolerance for large values and is what you should reach for in real code.

    def close_enough(a, b, tol=1e-9):
        return abs(a - b) < tol
    
    # The standard-library version, which also scales with magnitude:
    import math
    math.isclose(0.1 + 0.2, 0.3)
    Check your work

    Paste this after your own code. If it runs without raising, you have it.

    assert (0.1 + 0.2) != 0.3
    assert close_enough(0.1 + 0.2, 0.3)
    assert not close_enough(0.1, 0.2)
    print("ok")
  3. Using an f-string, print a learning rate of 0.000125 in scientific notation with two decimal places, and a percentage of 0.8734 as "87.3%".
    Hint

    The format spec goes after a colon inside the braces: {value:.2e}.

    Solution

    Use `:.2e` for scientific notation and `:.1%` for a percentage — the percent format multiplies by 100 and appends the sign for you, so do not multiply first.

    lr = 0.000125
    acc = 0.8734
    
    print(f"{lr:.2e}")     # 1.25e-04
    print(f"{acc:.1%}")    # 87.3%
    Check your work

    Paste this after your own code. If it runs without raising, you have it.

    assert f"{0.000125:.2e}" == "1.25e-04"
    assert f"{0.8734:.1%}" == "87.3%"
    print("ok")

Sign in to track your progress through the lab.