{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Handle money with Decimal\n",
    "\n",
    "**The question.** You're working with money — prices, totals, tax, discounts — and you need every figure to be exact to the penny. A `float` can't do this (`0.1 + 0.2 != 0.3`), so the answer is `Decimal` from start to finish.\n",
    "\n",
    "The pattern is always the same four steps: **build from strings**, do arithmetic in `Decimal`, `quantize` to two places at the end, and format for display. Below is the whole workflow."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 1: build amounts from strings\n",
    "\n",
    "Always construct money `Decimal`s from strings (or integers), never from floats — `Decimal(0.1)` imports the float's error, `Decimal('0.10')` is exact. Reading prices as text (from a form, a CSV, a database) keeps them exact end to end."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "from decimal import Decimal\n",
    "\n",
    "price = Decimal('19.99')\n",
    "quantity = 3\n",
    "print(price, quantity)\n",
    "\n",
    "# from user/text input, this is already safe:\n",
    "amounts = [Decimal(s) for s in ['12.50', '4.99', '0.75']]\n",
    "print(amounts)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 2: do the arithmetic in Decimal\n",
    "\n",
    "`Decimal` supports the usual operators and works with `int`, so multiplying by a quantity or summing a basket is exact. `sum` works too — give it `Decimal('0')` as the start value to keep the type."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "from decimal import Decimal\n",
    "\n",
    "price = Decimal('19.99')\n",
    "subtotal = price * 3                 # 59.97 — exact\n",
    "basket = [Decimal('12.50'), Decimal('4.99'), Decimal('0.75')]\n",
    "basket_total = sum(basket, Decimal('0'))\n",
    "print(subtotal, basket_total)        # 59.97 18.24"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 3: apply tax/discounts, then quantize to pennies\n",
    "\n",
    "Intermediate results can have many decimal places (20% of 59.97 is 11.994), and that's fine — keep full precision *during* the calculation. Only at the end do you round to two places, with `ROUND_HALF_UP`, using a pennies template."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "from decimal import Decimal, ROUND_HALF_UP\n",
    "\n",
    "PENNIES = Decimal('0.01')\n",
    "\n",
    "subtotal = Decimal('19.99') * 3       # 59.97\n",
    "tax = subtotal * Decimal('0.20')      # 11.9940 — full precision kept\n",
    "total = (subtotal + tax).quantize(PENNIES, rounding=ROUND_HALF_UP)\n",
    "print(f'subtotal {subtotal}, tax {tax}, total {total}')   # total 71.96"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Rounding **once at the end** matters: if you round every intermediate value, the little roundings accumulate and your total can drift by a penny or two. Compute exact, round last."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 4: format for display\n",
    "\n",
    "`quantize` guarantees two decimal places even when the amount is whole (`5.00`, not `5`). For display with a currency symbol and thousands separators, format the `Decimal` directly — f-strings accept it."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "from decimal import Decimal, ROUND_HALF_UP\n",
    "\n",
    "amount = (Decimal('1234') + Decimal('56.7')).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)\n",
    "print(amount)                 # 1290.70 — trailing zero kept\n",
    "print(f'£{amount:,.2f}')      # £1,290.70 — symbol + thousands separator"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Splitting a bill without losing a penny\n",
    "\n",
    "A classic exactness trap: £10.00 split three ways is £3.333... — you can't pay a third of a penny. Round each share down, then hand the leftover pennies to the first few payers, so the parts sum back to the whole."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "from decimal import Decimal, ROUND_DOWN\n",
    "\n",
    "def split_evenly(total, n):\n",
    "    total = Decimal(str(total)).quantize(Decimal('0.01'))\n",
    "    share = (total / n).quantize(Decimal('0.01'), rounding=ROUND_DOWN)\n",
    "    shares = [share] * n\n",
    "    remainder = total - share * n            # the leftover pennies\n",
    "    pennies = int(remainder / Decimal('0.01'))\n",
    "    for i in range(pennies):                 # distribute them one each\n",
    "        shares[i] += Decimal('0.01')\n",
    "    return shares\n",
    "\n",
    "shares = split_evenly('10.00', 3)\n",
    "print(shares)                # [Decimal('3.34'), Decimal('3.33'), Decimal('3.33')]\n",
    "print(sum(shares))           # 10.00 — adds back exactly"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## The rules, in short\n",
    "\n",
    "- Build money `Decimal`s from **strings**, never floats.\n",
    "- Keep the whole calculation in `Decimal` — mixing in a `float` raises `TypeError` (a useful guardrail).\n",
    "- Keep full precision while computing; `quantize` to `0.01` **once, at the end**, with `ROUND_HALF_UP`.\n",
    "- When splitting, round the parts and redistribute the remainder so they sum back to the total.\n",
    "- Format with `f'£{amount:,.2f}'` for display."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
