{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Numeric types\n",
    "\n",
    "Python has three built-in numeric types — `int`, `float`, and `complex` — and they cover almost everything. This notebook is about what each one is, how to write number literals clearly, and how the arithmetic operators behave (including the two or three that surprise people). The exact types `Decimal` and `Fraction` come in a [later notebook](https://agilearn.co.uk/guides/numbers-and-maths/learn/03-decimal-and-fraction); here we stay with the built-ins."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The three built-in types\n",
    "\n",
    "`int` for whole numbers, `float` for numbers with a fractional part, `complex` for numbers with an imaginary component. Each has its own literal syntax, and `type()` tells you which you've got."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "a = 42          # int\n",
    "b = 3.14        # float\n",
    "c = 2 + 3j      # complex (j is the imaginary unit)\n",
    "\n",
    "print(type(a), type(b), type(c))\n",
    "print(c.real, c.imag)        # complex numbers carry two floats"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## `int` never overflows\n",
    "\n",
    "Unlike most languages, Python's `int` has no fixed size — it grows to fit whatever you compute. There's no 32- or 64-bit ceiling, no overflow, no wraparound. The only limit is your machine's memory."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "print(2 ** 100)                 # a 31-digit number, no problem\n",
    "\n",
    "big = 1\n",
    "for n in range(1, 51):\n",
    "    big *= n                    # 50! — fifty factorial\n",
    "print(big)\n",
    "print(big.bit_length(), 'bits')  # how many binary digits it takes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`float`, by contrast, *is* fixed-size (64-bit), which is the source of all the behaviour in the [next notebook](https://agilearn.co.uk/guides/numbers-and-maths/learn/02-floating-point). For now, just note the asymmetry: integers are exact and unbounded; floats are approximate and bounded."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Writing number literals clearly\n",
    "\n",
    "A few syntax features make numeric literals easier to read and write:\n",
    "\n",
    "- **Underscores** group digits — Python ignores them, they're purely for humans.\n",
    "- **Prefixes** write integers in other bases: `0x` hex, `0o` octal, `0b` binary.\n",
    "- **Scientific notation** (`1e6`) writes floats — note the result is always a `float`."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "population = 8_100_000_000      # underscores: same as 8100000000\n",
    "print(population)\n",
    "\n",
    "print(0xff, 0o17, 0b1010)       # 255, 15, 10 — all written in other bases\n",
    "\n",
    "print(1e6, type(1e6))           # 1000000.0 — scientific notation is a float\n",
    "print(1.5e-3)                   # 0.0015"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To go the other way — turn an `int` into its base representation as a string — use `bin`, `hex`, and `oct`. To parse a string in a given base, pass the base to `int`."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "print(bin(10), hex(255), oct(15))   # '0b1010' '0xff' '0o17'\n",
    "print(int('ff', 16), int('1010', 2))  # parse: 255, 10"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The arithmetic operators\n",
    "\n",
    "The usual suspects, plus three worth dwelling on: `/` always gives a `float`, `//` is *floor* division, and `%` is the remainder that pairs with it."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "print(7 + 2, 7 - 2, 7 * 2)      # 9 5 14\n",
    "print(7 / 2)                    # 3.5  — true division ALWAYS returns a float\n",
    "print(7 // 2)                   # 3    — floor division, rounds toward -inf\n",
    "print(7 % 2)                    # 1    — remainder\n",
    "print(2 ** 10)                  # 1024 — exponentiation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`10 / 2` is `5.0`, not `5` — `/` gives a float even when the result is whole. Reach for `//` when you want an integer result (indices, counts, page numbers)."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "print(10 / 2, type(10 / 2))     # 5.0 <class 'float'>\n",
    "print(10 // 2, type(10 // 2))   # 5 <class 'int'>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Floor division and remainder with negatives\n",
    "\n",
    "`//` rounds *down* (toward negative infinity), not toward zero — so `-7 // 2` is `-4`, not `-3`. The `%` operator is defined to match, so the identity `(a // b) * b + (a % b) == a` always holds. A handy consequence: in Python `a % b` always has the same sign as `b`, which makes `x % n` reliable for wrapping into a range."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "print(-7 // 2)                  # -4  (rounds toward negative infinity)\n",
    "print(-7 % 2)                   # 1   (same sign as the divisor)\n",
    "print(divmod(-7, 2))            # (-4, 1) — quotient and remainder together\n",
    "\n",
    "# the identity that ties them together:\n",
    "print((-7 // 2) * 2 + (-7 % 2)) # -7"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`divmod(a, b)` returns `(a // b, a % b)` in one call — useful when you need both, like converting seconds into minutes-and-seconds."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "total_seconds = 197\n",
    "minutes, seconds = divmod(total_seconds, 60)\n",
    "print(f'{minutes}m {seconds}s')    # 3m 17s"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Mixing types in one expression\n",
    "\n",
    "When an expression mixes types, Python widens to the more general one, so you don't lose information: `int` + `float` → `float`. Combine an `int` and a `complex` and you get a `complex`. You rarely think about it, but it's why `1 + 2.0` is `3.0`."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "print(1 + 2.0)                  # 3.0   — int + float -> float\n",
    "print(3 / 2)                    # 1.5   — float result\n",
    "print((2 + 0j) + 3)             # (5+0j) — anything + complex -> complex"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## `bool` is a kind of `int`\n",
    "\n",
    "`True` and `False` are integers in disguise — `True == 1` and `False == 0`, and they behave as such in arithmetic. This is occasionally a footgun, but more often a feature: summing a list of booleans counts how many are true."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "print(True == 1, False == 0)        # True True\n",
    "print(True + True + False)          # 2\n",
    "print(isinstance(True, int))        # True — bool is a subclass of int\n",
    "\n",
    "votes = [True, False, True, True, False]\n",
    "print(sum(votes), 'yes')            # 3 yes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Chained comparisons\n",
    "\n",
    "Python lets you chain comparison operators the way maths notation does — `0 < x < 10` means exactly what it looks like, and each operand is evaluated once."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "x = 7\n",
    "print(0 < x < 10)               # True\n",
    "print(0 < x < 5)                # False\n",
    "\n",
    "# reads naturally for range checks\n",
    "score = 85\n",
    "print('pass' if 50 <= score <= 100 else 'fail')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The numeric built-ins\n",
    "\n",
    "A handful of built-in functions cover the common operations without importing anything: `abs`, `round`, `pow`, `min`, `max`, and `sum`."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "print(abs(-5), abs(3 - 7))      # 5 4\n",
    "print(round(3.14159, 2))        # 3.14 — round to 2 decimal places\n",
    "print(pow(2, 10))               # 1024 — same as 2 ** 10\n",
    "print(pow(2, 10, 1000))         # 24   — (2**10) % 1000, computed efficiently\n",
    "\n",
    "nums = [4, 1, 7, 3]\n",
    "print(min(nums), max(nums), sum(nums))   # 1 7 15"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`round` has a surprise hiding in it (`round(2.5)` is `2`, not `3`) and `pow`'s three-argument form is genuinely useful for modular arithmetic — both are covered in the [floating point notebook](https://agilearn.co.uk/guides/numbers-and-maths/learn/02-floating-point) and the [rounding recipe](https://agilearn.co.uk/guides/numbers-and-maths/recipes/round-numbers-correctly)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Recap\n",
    "\n",
    "- Three built-in types: `int` (exact, unbounded), `float` (approximate, 64-bit), `complex`.\n",
    "- Literals: underscores for grouping, `0x`/`0o`/`0b` for bases, `1e6` for floats.\n",
    "- `/` always returns a `float`; `//` floors toward negative infinity; `%` matches it; `divmod` gives both.\n",
    "- Mixed expressions widen to the more general type; `bool` is an `int`.\n",
    "\n",
    "Next: [Floating point](https://agilearn.co.uk/guides/numbers-and-maths/learn/02-floating-point) — why `float` arithmetic isn't quite what it seems, and what to do about it."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}