← Home

027. Operator Precedence

Explicit parentheses prevent ambiguity

027. Operator Precedence

RAM Manager — Memory Formulas

Aryan’s RAM manager computes several metrics from raw numbers. He transcribes the formulas directly from the spec sheet — but forgets to add parentheses where needed, and Python’s default operator precedence gives the wrong answer.

  • available_ram: total RAM minus (OS overhead + reserved). Written as total - overhead + reserved, which subtracts overhead but then adds reserved instead of subtracting it.
  • usage_after_discount: used * 1 - discount_mb should be used * (1 - discount_mb / total) — without parentheses, * binds first.
  • effective_bandwidth: a weighted average formula where the denominator must be grouped.

The lesson: Python follows PEMDAS like C, but missing parentheses are silent bugs. The code runs, produces a number, and looks plausible — it is just wrong.


💡 Fun fact: The classic average bug a + b + c / 3 (dividing only c instead of the whole sum) has caused real-world disasters — a similar precedence error in a NASA Mars Climate Orbiter mission contributed to a $327 million spacecraft being lost in 1999, because unit conversions were applied at the wrong point in a formula.

⚠️ Watch out: The most frequent precedence mistake is writing an average without parentheses: a + b + c / 3 divides only c by 3. Always write (a + b + c) / 3. When in doubt, add parentheses — they cost nothing and make your intent explicit to every reader.

🤔 Think about it: Python’s ** operator is right-associative, meaning 2 ** 3 ** 2 = 2 ** 9 = 512, not 8 ** 2 = 64. Most other operators are left-associative. Why might the designers have chosen right-associativity specifically for exponentiation?

Learning objectives

  • Know the basic Python operator precedence order
  • Use parentheses to explicitly control evaluation order
  • Identify common precedence mistakes (average, ring area, discounts)

Key concepts

  • operator precedence
  • parentheses
  • PEMDAS

Try it

Concept detail

Python operator precedence (highest first): ** > unary (+x, -x, ~x) > * / // % > + - > comparisons > not > and > or. When in doubt: use parentheses — they cost nothing and make intent explicit. 2 + 3 * 4 = 14 (not 20): multiplication binds before addition. a + b + c / 3 divides only c, not the sum — a classic average bug. 2 ** 3 ** 2 = 512: ** is right-associative, so 2**(32) = 29. -(2**2) = -4 (not 4): ** binds tighter than unary minus.

Solution

import math

def area_of_ring(outer_r, inner_r):
    return math.pi * (outer_r ** 2 - inner_r ** 2)

def discount_price(price, discount_pct):
    return price * (1 - discount_pct / 100)

def average_of_three(a, b, c):
    return (a + b + c) / 3

Tests

import math

def test_area_of_ring():
    # outer=5, inner=3 → π*(25-9) = π*16 ≈ 50.27
    result = area_of_ring(5, 3)
    assert abs(result - math.pi * 16) < 0.001

def test_discount_price():
    # 100 * (1 - 20/100) = 100 * 0.8 = 80
    result = discount_price(100, 20)
    assert abs(result - 80.0) < 0.001

def test_average_equal():
    # (3 + 3 + 3) / 3 = 3.0
    result = average_of_three(3, 3, 3)
    assert abs(result - 3.0) < 0.001

def test_average_mixed():
    # (1 + 2 + 3) / 3 = 2.0
    result = average_of_three(1, 2, 3)
    assert abs(result - 2.0) < 0.001

Resources