# December 4: Python Numeric and Mathematical Modules

Today is a bit less bad than yesterday: As part of the Python Standard Library traversal, after yesterday's extensive post about data types, today we're going to look at Python modules that take care of numbers and maths and make us very happy because we don't need to implement them ourselves. Or wait for NumPy to install.

### Highlights

• The `numbers` module contains abstract base classes so you can check for features of given numbers using `isinstance`.
• The Python documentation takes a stand for `tau` and against `pi`. Good documentation.
• `cmath` provides mathematical operations and functions for complex numbers – it exists separately from `math` so that people aren't surprised when they run `math.sqrt(-1)`. 🤣
• `fractions.limit_denominator()` allows you to take a float and turn it into the number it was meant to be. Go team `fractions`.
• The fact that `decimal` is singular and `fractions` is plural will never cease to confuse me.
• Apparently there's both `random.gauss()` and `random.normalvariate()` ???
• `statistics.NormalDist` creates a normal distribution either from a set of samples or the mean and a standard deviation.

### numbers

The `numbers` module contains abstract base classes, so you can check for features of given numbers using `isinstance`. These classes are `Number` (the root class), `Complex` (guarantees `real`, `imag` and `conjugate()`), `Real`, `Rational` (guarantees `numerator` and `denominator`) and `Integral`.

### math

Here you'll find mathematical functions defined by the C standard. Those include basic functions like `ceil()` and `floor()`, `trunc()`, `remainder()` and `prod()`, `isclose()`, `factorial()` and `comb()` (binomial coefficient), `gcd()` (greatest common divisor) and `lcm()` (least common multiple, new in 3.9). Square root, logarithmic functions and exponential/power functions, trig functions and conversion between degree and radians, plus some functions I've never heard of. Additionally, you get `pi`, `e`, `tau` plus advertisement for tau and against pi, `inf` and `nan`.

There's also a bit of floating point handling, as of 3.9: `ulp()` returns the value of the least siginificant bit of a given number ("unit in the last place"), and `nextafter()` returns the next float value from a in direction b.

### cmath

`cmath` contains helper functions for dealing with complex numbers – exactly like `math`, only that all return values are always complex numbers, even when they could be expressed as real numbers. Use `polar()` and `rect()` to convert between the two different coordinate representations. There are a ton of calculation functions: `exp`, `log`, `log10`, `sqrt`, all the trigonometric functions, and `isclose()` to see if two complex numbers are close to each other.

This module exists to avoid confusing the average `math` module user, who would not expect `math.sqrt(-1)` to actually return a result. 🤣

### decimal

Who wants floating point arithmetic that doesn't constantly try to screw you over? I do! `decimal.Decimal` is here for all of us.

#### Decimals

Decimals are immutable objects with a sign, a coefficient and an exponent. They generally follow `float` norms in also understanding about positive and negative zero and infinity. They are instantiated with a number, a string or a tuple. You usually do not want to pass `float` values, and instead go with their string representation. You can get the most significant digit with `.adjusted()`, get the value of `e**x` with `exp()`, run `log10()` and `ln()`, and run all sorts of arithmetic operations – you'd choose to use the `decimal` object methods to make sure you're dealing with the precision you want. Run `normalize()` if you want to make sure the number is as readable as possible. Use helper methods like `is_finite()`, `is_nan()`, `is_signed()`, `is_zero()` and the like to learn more about a number.

#### Context

All decimal operations take place in a context, which you can change. The context specifies precision, rounding rules, limits on exponents, flags indicating the results of operations, and trap enablers which determine whether signals are treated as exceptions. You can use `getcontext()` and `setcontext()` to interact with it, or use the `localcontext` context manager to change it just for a while. Read the docs for a list of rounding flags, trap/signal flags and other attributes (like `prec`) that you can change with the context.

#### Signals

Signals can be ignored, treated as information or as errors. They arise when "exceptional conditions arise" during computation, such as inexact operations, division by zero or overflows.

Every thread retains its own context object, so you can change your decimal context without influencing other threads.

### fractions

You know how floats are evil and bad? Fractions are not evil and not bad! You can instantiate them from two numbers, or a single number, or even a string. You can do all sorts of math on them and run `as_integer_ratio()` (much more useful than on `float`s!). Even more useful: `limit_denominator()` returns the closest fraction with a denominator smaller than `n=100000`, which is great for converting a `float` to the number it was meant to be!

### random

All `random` generators share the same base generator, `random.Random`, which you can instantiate if you need a separate generator. Almost all functions rely on `random.random()` (which returns values between 0 and 1), and then runs some transformations on it. The generators are pseudo-random and should not be used for security purposes.

#### Bookkeeping functions

`random.seed()` starts the generator with a seed or with system time (use for reproducibility). With `getstate()` and `setstate()`, you can manipulate the generator status.

The main generator functions are `randrange()` for integers, `choice()`, `choices()`, `shuffle()` and `sample()` for sequences. For real-value distributions, there's the base `random()` for the 0, 1 interval, `randbytes()` and `randbits()` for n random bytes or bytes (new in 3.9), `uniform()` for any interval, `triangular()` for a weighted interval, `gauss()` and `normalvariate()` and a lot of other distributions. Wish I knew enough math to appreciate it.

#### Alternative Generator

Normal `random` methods use `random.Random` as generator, but you can also use `random.SystemRandom`, which uses `os.urandom()` if it is available.

### statistics

This is basically our scientific calculator – not a competitor with NumPy, but making sure we can draw graphs and conclusions. Functions support `int`, `float`, `Decimal` and `Fraction`.

The statistics package includes different sorts of `mean()` and `median()` calculations, plus `mode`, `multimode` and `quantile` functions. It also has measures for spread, such as `stdev()` and `variance()`. Additionally, there is a `NormalDist` class that creates a standard distribution from either a mean and a standard deviation (boring) or a set of samples (woo-hoo!)