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.
numbersmodule contains abstract base classes so you can check for features of given numbers using
- The Python documentation takes a stand for
pi. Good documentation.
cmathprovides mathematical operations and functions for complex numbers – it exists separately from
mathso that people aren't surprised when they run
fractions.limit_denominator()allows you to take a float and turn it into the number it was meant to be. Go team
- The fact that
decimalis singular and
fractionsis plural will never cease to confuse me.
- Apparently there's both
statistics.NormalDistcreates a normal distribution either from a set of samples or the mean and a standard deviation.
numbers module contains abstract base classes, so you can check for features of given numbers using
These classes are
Number (the root class),
Here you'll find mathematical functions defined by the C standard. Those include basic functions like
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
tau plus advertisement for tau and against pi,
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 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
rect() to convert
between the two different coordinate representations. There are a ton of calculation functions:
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. 🤣
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 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
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
is_zero() and the like to learn more about a number.
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
setcontext() to interact with it, or use the
context manager to change it just for a while. Read the docs for a list of rounding flags, trap/signal flags and other
prec) that you can change with the context.
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.
Working with threads
Every thread retains its own context object, so you can change your decimal context without influencing other threads.
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
floats!). 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 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.
random.seed() starts the generator with a seed or with system time (use for reproducibility). With
setstate(), you can manipulate the generator status.
The main generator functions are
randrange() for integers,
sequences. For real-value distributions, there's the base
random() for the 0, 1 interval,
randbits() for n random bytes or bytes (new in 3.9),
uniform() for any interval,
triangular() for a weighted
normalvariate() and a lot of other distributions. Wish I knew enough math to appreciate it.
random methods use
random.Random as generator, but you can also use
random.SystemRandom, which uses
os.urandom() if it is available.
This is basically our scientific calculator – not a competitor with NumPy, but making sure we can draw graphs and
conclusions. Functions support
The statistics package includes different sorts of
median() calculations, plus
quantile functions. It also has measures for spread, such as
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