{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "# Chapter 5: Bits & Numbers" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Coding Exercises" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Read [Chapter 5](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/05_numbers_00_lecture.ipynb) of the book. Then, work through the exercises below. The `...` indicate where you need to fill in your answers. You should not need to create any additional code cells." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Discounting Customer Orders (revisited)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The \"*Volume of a Sphere*\" problem in [Chapter 2's Exercises](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/02_functions_02_exercises.ipynb#Volume-of-a-Sphere) section revealed that we must consider the effects of the `float` type's imprecision.\n", "\n", "This becomes even more important when we deal with numeric data modeling accounting or finance data (cf., [this comment](https://stackoverflow.com/a/24976426) on \"falsehoods programmers believe about money\").\n", "\n", "In addition to the *inherent imprecision* of numbers in general, the topic of **[rounding numbers](https://en.wikipedia.org/wiki/Rounding)** is also not as trivial as we might expect! [This article](https://realpython.com/python-rounding/) summarizes everything the data science practitioner needs to know.\n", "\n", "In this exercise, we revisit the \"*Discounting Customer Orders*\" problem from [Chapter 3's Exercises](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals_02_exercises.ipynb#Discounting-Customer-Orders) section and make the `discounted_price()` function work *correctly* for real-life sales data." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Q1**: Execute the code cells below! What results would you have *expected*, and why?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "round(1.5)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "round(2.5)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "round(2.675, 2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Q2**: The built-in [round()](https://docs.python.org/3/library/functions.html#round) function implements the \"**[round half to even](https://en.wikipedia.org/wiki/Rounding#Round_half_to_even)**\" strategy. Describe in one or two sentences what that means!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Q3**: For the revised `discounted_price()` function, we have to tackle *two* issues: First, we have to replace the built-in `float` type with a data type that allows us to control the precision. Second, the discounted price should be rounded according to a more human-friendly rounding strategy, namely \"**[round half away from zero](https://en.wikipedia.org/wiki/Rounding#Round_half_away_from_zero)**.\"\n", "\n", "Describe in one or two sentences how \"**[round half away from zero](https://en.wikipedia.org/wiki/Rounding#Round_half_away_from_zero)**\" is more in line with how humans think of rounding!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Q4**: We use the `Decimal` type from the [decimal](https://docs.python.org/3/library/decimal.html) module in the [standard library](https://docs.python.org/3/library/index.html) to tackle *both* issues simultaneously.\n", "\n", "Assign `euro` a numeric object such that both `Decimal(\"1.5\")` and `Decimal(\"2.5\")` are rounded to `Decimal(\"2\")` (i.e., no decimal) with the [quantize()](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) method!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from decimal import Decimal" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "euro = ..." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Decimal(\"1.5\").quantize(...)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Decimal(\"2.5\").quantize(...)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Q5**: Obviously, the two preceding code cells still [round half to even](https://en.wikipedia.org/wiki/Rounding#Round_half_to_even).\n", "\n", "The [decimal](https://docs.python.org/3/library/decimal.html) module defines a `ROUND_HALF_UP` flag that we can pass as the second argument to the [quantize()](https://docs.python.org/3/library/decimal.html#decimal.Decimal.quantize) method. Then, it [rounds half away from zero](https://en.wikipedia.org/wiki/Rounding#Round_half_away_from_zero).\n", "\n", "Add `ROUND_HALF_UP` to the code cells! `Decimal(\"2.5\")` should now be rounded to `Decimal(\"3\")`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from decimal import ROUND_HALF_UP" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Decimal(\"1.5\").quantize(...)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Decimal(\"2.5\").quantize(...)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Q6**: Instead of `euro`, define `cents` such that rounding occurs to *two* decimals! `Decimal(\"2.675\")` should now be rounded to `Decimal(\"2.68\")`. Do *not* forget to include the `ROUND_HALF_UP` flag!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cents = ..." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Decimal(\"2.675\").quantize(...)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Q7**: Rewrite the function `discounted_price()` from [Chapter 3's Exercises](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals_02_exercises.ipynb#Discounting-Customer-Orders) section!\n", "\n", "It takes the *positional* arguments `unit_price` and `quantity` and implements a discount scheme for a line item in a customer order as follows:\n", "\n", "- if the unit price is over 100 dollars, grant 10% relative discount\n", "- if a customer orders more than 10 items, one in every five items is for free\n", "\n", "Only one of the two discounts is granted, whichever is better for the customer.\n", "\n", "The function then returns the overall price for the line item as a `Decimal` number with a precision of *two* decimals.\n", "\n", "Enable **duck typing** by allowing the function to be called with various numeric types as the arguments, in particular, `quantity` may be a non-integer as well: Use an appropriate **abstract base class** from the [numbers](https://docs.python.org/3/library/numbers.html) module in the [standard library](https://docs.python.org/3/library/index.html) to verify the arguments' types and also that they are both positive!\n", "\n", "It is considered a *best practice* to only round towards the *end* of the calculations." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numbers" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def discounted_price(...):\n", " ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Q8**: Execute the code cells below and verify the final price for the following four test cases:\n", "\n", "- $7$ smartphones @ $99.00$ USD\n", "- $3$ workstations @ $999.00$ USD\n", "- $19$ GPUs @ $879.95$ USD\n", "- $14$ Raspberry Pis @ $35.00$ USD\n", "\n", "The output should now *always* be a `Decimal` number with *two* decimals!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "discounted_price(99, 7)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "discounted_price(999, 3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "discounted_price(879.95, 19)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "discounted_price(35, 14)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This also works if `quantity` is passed in as a `float` type." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "discounted_price(99, 7.0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Decimals beyond the first two are gracefully discarded (i.e., *without* rounding errors accumulating)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "discounted_price(99.0001, 7)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The basic input validation ensures that the user of `discounted_price()` does not pass in invalid data. Here, the `\"abc\"` creates a `TypeError`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "discounted_price(\"abc\", 7)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A `-1` passed in as `unit_price` results in a `ValueError`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "discounted_price(-1, 7)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": false, "sideBar": true, "skip_h1_title": true, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": false, "toc_window_display": false } }, "nbformat": 4, "nbformat_minor": 4 }