# Python: Numerical Types, Mathematical Functions & The Random Module

This is part of my Python & Django Series which can be found here including information on how to download all the source code.

## Numbers

Python and its dynamic type system means that in most cases you don’t need to worry about the data type you are working on.

Before you get too excited there are still inherent inaccuracies when working with floating point numbers!

One thing I really like about Python is that from version 3 operators are not truncated to the narrowest type, as such 3.3 / 1 = 1.1 and not 1!

Python provides the following numerical types which are all immutable.

Type Description Example
int Integral value x = 10
float Floating point x = 1.1
Decimal Decimal point x = Decimal(1.1)
boolean Sublcass of int x = True
Fraction Holds a separate numerator and denominator x = Fraction(1, 2)
complex Representation of a theoretical complex numbers x = complex(-1, 0, 0.0 )

They all respond to the basic mathematical and assignment operators. TODO

### Integers

Before Python version 3 there were two integer types; int and long. Ints had a defined range while longs were in theory infinite but in practice depended upon the size of the available memory. The Python runtime determined which type was required and for longs handled the amount of memory required.

For version 3 and greater the int and long types have been merged into the int type. The runtime handles determining the memory size based upon the size of the integer being represented.

In theory ints can grow as big as they are required; the only limit is the amount of available memory.

Assignment is made with the = operator and an integral value. The int constructor is implicitly called though can be used.

Code:

```x = 1
y = int(1)
```

We can determine the type with the type function.

Code:

```print(x, type(x))
```

Output:

1

We can parse strings to an int via the constructor.

Code:

```x = int("1")
```

We can check that a string only contains characters which can be parsed with the isdigit function.

Code:

```print(str.isdigit("1"));
print(str.isdigit("a"));
```

Output:

True
False

We can round by passing in a negative number of decimal places.

Below we round a value to -2 decimal places; the first two digits to the left of the decimal point become zeros, the third is rounded accordingly.

Code:

```print(round(11111, -2))
```

Output:

11100

We can perform many maths operators, below are some examples. . See ../Operators/*.py for more details *TODO**

Code:

```print(1+1)
print(1-1)
print(1/1)
print(1*1)
```

Output:

2
0
1.0
1

We can get physical storage information about the int class with the int_info function.

Code:

```import sys
print(print(sys.int_info))
```

The output will vary depending if you are using a 32bit or 64bit operating system.

Output:

sys.int_info(bits_per_digit=30, sizeof_digit=4)
sys.int_info(bits_per_digit=15, sizeof_digit=2)

64 bit systems will store a digit as 2**30 bits and 32 bit systems will store a digit as 2**15 bits. Both will allow an int to grow to an infinite size dependant on memory.

The memory used is dependant upon what is required. We can use the getsizeof function to get the size of the memory allocated for an instance of an int.

Code:

```from sys import getsizeof

print(getsizeof(0))
print(getsizeof(100**100))
print(getsizeof(100**100100))
```

Output:

24
116
88700

### Floating Point

The float type represents a floating point number.

A float is a fixed size representation of a fractional number; it contains digits to the left and right of the decimal point.

1/3 gives us an infinite number of digits after the decimal point which is impossible to store exactly.

Float has a fixed memory size and it’s range is represented by significant figures rather than a physical min and max boundary.

For example 1.0e10 = 10000000000.0 and only has one digit of signification.

Format Total bits Significant bits Exponent bits
Single precision 32 23 + 1 sign 8
Double precision 64 52 + 1 sign 11

Assignment is made with the = operator and any real number i.e. not an integral. It is a short cut for the float constructor which can be implicitly called.

Code:

```x = 1.1
y = float(1.1)
```

We can determine the type with the type function.

Code:

```print(x, type(1))
```

Output:

1.1
1

We can parse strings to a float including exponential representations.

Code:

```print(float("1"))
print(float("1.0e10"))
```

Output:

1.0
10000000000.0

We can round to x d.p with the round function.

Code:

```print(round(1.11111, 2))
```

Output:

1.11

We can perform many maths operators. TODO. See ../Operators/*.py for more details.

Code:

```print(1.1 + 1.1)
print(1.1 - 1.1)
print(1.1 / 1.1)
print(1.1 * 1.1)
```

Output:

2.2
0.0
1.0
1.2100000000000002

We can get physical storage information about the float class with the float_info function.

Code:

```print(float_info)
print(sys.float_info.min)
print(sys.float_info.max)
```

Output:

sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1)
2.2250738585072014e-308
1.7976931348623157e+308

For 64 bit machines we have 53 bits of signification as noted with the mant_dig property, this includes the sign!

The memory appears to always fixed at 24 bytes, incrementing over this size causes an OverflowError exception to be raised.

Code:

```print(getsizeof(float(0)))
print(getsizeof(1.1))
print(getsizeof(float(9999999.9)))
```

Output:

24
24
24

As per all languages floats are approximations and as such suffer from inaccuracy.

Code:

```print(1 / 3)
print(.1 + .1 + .1 == .3)
print(float(.1) + float(.1) + float(.1))
```

Output:

0.3333333333333333
False
0.30000000000000004

The following pages explain more about floats and their inherent issues.

### Decimal

The decimal type represents a real number (integral and fraction) and has a closer representation to the real value when compared to float.

It is not optimised for computers and as such has a memory and performance hit when compared to floats though they are more accurate.

In python decimals can grow to take on more precision and accuracy as required.

Assignment is made with the = operator and the decimal constructor.

Code:

```from decimal import Decimal

x = Decimal(1.1)
```

We can determine the type with the type function.

Code:

```x = print(x, type(Decimal(1.1))
```

Output:

1.100000000000000088817841970012523233890533447265625

We can parse strings to a Decimal including exponential numbers.

Code:

```print(Decimal("1"))
print(Decimal("1.0e10"))
```

Output:

1
1.0E+10

We can round to x d.p with the round function.

Code:

```print(round(Decimal(1.111111), 2))
```

Output:

1.11

We can perform many maths operators. TODO ee ../Operators/*.py for more details

Code:

```x = Decimal(1.1)
print(x + x)
print(x - x)
print(x / x)
print(x * x)
```

Output:

2.200000000000000177635683940
0E-51
1
1.210000000000000195399252334

We can use the getcontext function to get information about the physical memory representation of a Decimal.

Code:

```from decimal import Decimal, getcontext

print(getcontext())
```

Output:

Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[Inexact, FloatOperation, Rounded], traps=[InvalidOperation, DivisionByZero, Overflow])

The memory allocation seems to always be fixed as 24 bytes.

Code:

```print(getsizeof(Decimal(0)))
print(getsizeof(Decimal(1024)))
print(getsizeof(Decimal(999999999999)))
```

Output:

104
104
104

Decimals provide better accuracy to floats.

Code:

```print(.1 + .1 + .1 == .3)
print(Decimal(".1") + Decimal(".1") + Decimal(".1") == Decimal(".3"))
```

Output:

False
True

### Fractions

Represents a fraction i.e it has a nominator and a denominator.

A Fraction instance can be constructed from a pair of integers, from another rational number or from a string.

Code:

```from fractions import Fraction
from decimal import Decimal

print(Fraction(1, 2))
print(Fraction(Fraction(1, 2)))
print(Fraction(1.1))
print(Fraction(Decimal(1.1)))
```

Output:

1/2
1/2
2476979795053773/2251799813685248
2476979795053773/2251799813685248

We can determine the type with the type function.

Code:

```x = Fraction(1, 2)
print(x, type(x))
```

Output:

1/2

We can parse strings of rational number into a fraction.

Code:

```print(Fraction("1.1"))
```

Output:

11/10

We can perform many maths operators. TODO: See ../Operators/*.py for more details

Code:

```x = Fraction(1, 2)
print(x + x)
print(x - x)
print(x / x)
print(x * x)

```

Output:

1
0
1
1/4

### Complex Numbers

Complex numbers have a real and imaginary part both of which which are floating point numbers.

Assignment is made with the = operator and the complex constructor.

Code:

```x = complex(1.1, 2.2)
```

We can determine the type with the type function.

Code:

```print(x, type(x))
```

Output:

(1.1+2.2j)

We can perform many maths operators. TODO: # See ../Operators/*.py for more details

Code:

```x = complex(1.1, 2.2)
print(x + x)
print(x - x)
print(x / x)
print(x * x)
```

Output:

(2.2+4.4j)
0j
(1+0j)
(-3.630000000000001+4.840000000000001j)

## Math

The math module contains various useful basic maths functions.

Code:

```import math
```

The ceil and floor functions can be used to round up and down to the nearest integer respectively. Where negative numbers are found they work away from zero.

Code:

```print(math.ceil(11.11))
print(math.floor(11.11))
print(math.ceil(-11.11))
print(math.floor(-11.11))

```

Output:

12
11
-11
-12

The min and max functions can be used to return the largest and smallest valued entity from any number of arguments passed in respectively. They can both work from collections.

The fsum function can be used to sum all elements within a collection.

Code:

```print(min(1, 2, 3))
print(max(1, 2, 3))
print(math.fsum([1, 2, 3]))
```

Output:

1
3
6

Modf returns a tuple of a real number as an integral and fractal.

Trunc removes any digits after the decimal point leaving an integral value.

Fabs returns a positive value of a number or the number itself if it is not negative.

Code:

```print(math.modf(1.1))
print(math.trunc(1.11))
print(math.trunc(-1.11))
print(math.fabs(-999))
```

Output:

(0.10000000000000009, 1.0)
1
-1
999.0

Isfinite can be used to ensure a number is not NaN or infinite. Isinf can determine if a number is infinite while isnan can determine if a value is assigned nan.

Code:

```print(math.isfinite(1))
print(math.isinf(float("inf")))
print(math.isnan(float("inf")))
```

Output:

True
True
False

The function pow calculates one number to the power of another, while the sqrt function can be used to calculate the square root of a number.

Code:

```print(math.pow(3, 3))
print(math.sqrt(9))
```

Output:

9
3

The pi and e properties can be used to get the pi and e constants respectively.

Code:

```print(math.pi)      # Pi
print(math.e)        # E
```

The radians and degrees function can be used to convert degrees to radians and vice versa respectively.

Output:

3.141592653589793
2.718281828459045

Code:

```print(math.radians(360))                      # Degrees to Radian
```

Output:

6.283185307179586
360.0

Python provides many other trigonometry functions.

Function Description
acos(x) Return the arc cosine of x, in radians.
asin(x) Return the arc sine of x, in radians.
atan(x) Return the arc tangent of x, in radians.
cos(x) Return the cosine of x radians.
hypot(x, y) Return the Euclidean norm, sqrt(xx + yy).
sin(x) Return the sine of x radians.
tan(x) Return the tangent of x radians.

## Random

The random module allows functionality of random selections.

Random generates a random float which has no lower or upper limits.

Code:

```from random import random

print(random())
```

Output:

0.07197929300003614

Uniform generates a random float which has a lower and upper limit as defined by the first and second arguments respectively.

Code:

```from random import uniform

print(uniform(1, 10))
```

Output:

3.8361968102149504

Randint can be used to generate a random int which has a lower and upper limit.

Code:

```from random importrandint

print(randint(1, 99))
```

Output:

20

Randrange can be used to select an element which has a lower and upper limit but also respects an increment. Here we used it to get a random odd number between 1 and 99.

Code:

```from random import randrange

print(randrange(1, 99, 2))
```

Output:

71

Choice can be used to randomly select an element from an enumerable.

Code:

```from random import choice

print(choice('abcdefghi'))
```

Output:

h

Sample can be used to randomly select any number of elements from an enumerable. Here we select any two random elements from a collection.

Code:

```from random import sample

print(sample([1, 2, 3, 4, 5], 2))
```

Output:

[4, 2]

Shuffle can be used to randomly order an enumerable.

Code:

```from random import shuffle

letters = "a,b,c,d".split(',')
shuffle(letters)
print("Shuffled Letters:", letters)
```

Output:

[‘b’, ‘c’, ‘a’, ‘d’]