Three things to know about numbers in Python

If you have written code in Python, you must encounter numbers, such as integer as the index of the list, floating-point number to represent the current amount of wealth, and so on.

However, Python knows much more about numbers than that.

1. There are methods for digital

Everything in Python is an object. As you can see in many textbooks, it is almost the string object str taught as the first object. You must have checked its methods, such as changing all letters to lowercase lower() method.

>>> "HELLO".lower()
'hello'

There is no doubt that numbers are Python objects, so they must also have their own methods, such as using to_ The bytes () method converts an integer to a byte string.

>>> n = 255
>>> n.to_bytes(length=2, byteorder="big")
b'\x00\xff'

The parameter length is used to specify the byte length, and the parameter byteorder defines its own order. In the above example, byteorder="big" will rank the important bytes in the returned byte string, otherwise, byteorder="little".

255 is the largest 8-bit integer, so you can also set the length to 1:

>>> n.to_bytes(length=1, byteorder="big")
b'\xff'

However, if length=1 is also used for 256, overflow error will appear.

>>> n = 256
>>> n.to_bytes(length=1, byteorder="big")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: int too big to convert

You can also use the class method of int from_bytes() converts a byte string to an integer:

>>> int.from_bytes(b'\x06\xc1', byteorder="big")
1729

In addition to integers, floating point numbers also have methods, such as the most commonly used is_integer(), used to judge whether the floating-point number has a non-zero decimal part.

>>> n = 2.0
>>> n.is_integer()
True

>>> n = 3.14
>>> n.is_integer()
False

There is also an interesting floating point method as_integer_ratio(), which returns a tuple, including the numerator and denominator of the fraction corresponding to the floating-point number, or you can understand that this method is used to convert the floating-point number into a fraction.

>>> m = 2.5
>>> m.as_integer_ratio()
(5, 2)

Of course, due to errors in binary conversion (see the explanation in Python university practical tutorial for details), the following results will also appear.

>>> m = 0.1
>>> m.as_integer_ratio()
(3602879701896397, 36028797018963968)

In particular, when using the integer object method above, variables are used to reference the integer object. If so:

>>> 255.to_bytes(length=1, byteorder="big")
  File "<stdin>", line 1
    255.to_bytes(length=1, byteorder="big")
        ^
SyntaxError: invalid syntax

The solution is:

>>> (255).to_bytes(length=1, byteorder="big")
b'\xff'

However, for floating-point numbers, it is different:

>>> 3.14.is_integer()
False
>>> (3.14).is_integer()
False

Pay attention to the distinction.

2. There are levels of numbers

Mathematical numbers have levels. For example, all natural numbers are integers, all integers are rational numbers, all rational numbers are real numbers, and all real numbers are complex numbers.

Numbers in Python have a similar pyramid structure.

Hierarchical inheritance

All numbers in Python are instances of the Number class:

>>> from numbers import Number
>>> isinstance(1992, Number)
True
>>> isinstance(7.28, Number)
True
>>> isinstance(1j, Number)
True

If you only care about whether an object is a number, but don't care what kind of value it is, you can use isinstance(value, Number) for verification.

In the numbers module, there are four classes: Complex, Real, Rational and Tntegral, which correspond to the corresponding types in Python built-in objects:

  • The complex class is used to represent complex numbers. Complex type of built-in object: complex
  • The real class is used to represent real numbers. Real type of built-in object: float
  • The rotational class is used to represent rational numbers. Roational type of built-in object: Fraction
  • The Integral class is used to represent integers. Built in exclusive Integral types: int and bool

Yes, the value of bool is also a number.

>>> import numbers

# Complex inherited from complex
>>> isinstance(1j, numbers.Complex)
True

# Plural is not Real
>>> isinstance(1j, numbers.Real)
False

# Floating point numbers are Real
>>> isinstance(3.14, numbers.Real)
True

# Floating point numbers are not Rational
>>> isinstance(3.14, numbers.Rational)
False

# The score is Rational
>>> from fractions import Fraction
>>> isinstance(Fraction(1, 2), numbers.Rational)
True

# The score is not Integral
>>> isinstance(Fraction(1, 2), numbers.Integral)
False


# Integer is Integral
>>> isinstance(1729, numbers.Integral)
True

# Boolean is Integral
>>> isinstance(True, numbers.Integral)
True

>>> True == 1
True

>>> False == 0
True

Decimal is not in its column

In the above-mentioned Python, there are four numerical levels: Complex, Real, Rational and Tntegral, which do not contain Decimal type. It is special and is regarded as the fifth level.

Mathematically, decimals are Real numbers, but in Python, don't think numbers of Decimal type are Real.

>>> from decimal import Decimal
>>> import numbers

>>> isinstance(Decimal("3.14159"), numbers.Real)
False

In fact, objects of Decimal type only inherit the Number class.

>>> isinstance(Decimal("3.14159"), numbers.Complex)
False

>>> isinstance(Decimal("3.14159"), numbers.Rational)
False

>>> isinstance(Decimal("3.14159"), numbers.Integral)
False

>>> isinstance(Decimal("3.14159"), numbers.Number)
True

Strange floating point numbers

Floating point numbers are Real and represent Real numbers. However, due to memory constraints, floating point numbers are only an approximation of Real numbers, for example:

>>> 0.1 + 0.1 + 0.1 == 0.3
False

In addition, in Python, float("inf") and float("nan") are also special floating-point objects - numbers that are not numbers.

3. Digital scalability

Using the types of numbers in Python, such as those in numbers, you can define other numeric objects with special properties and methods.

For example, the ExtendedInteger class defined below represents a digital object in the form of a+b\sqrt{p}, where a and b are integers, p is not mandatory, and the default is 2.

import math
import numbers

class ExtendedInteger(numbers.Real):
    
    def __init__(self, a, b, p = 2) -> None:
        self.a = a
        self.b = b
        self.p = p
        self._val = a + (b * math.sqrt(p))
    
    def __repr__(self):
        return f"{self.__class__.__name__}({self.a}, {self.b}, {self.p})"
    
    def __str__(self):
        return f"{self.a} + {self.b}√{self.p}"
    
    def __trunc__(self):
        return int(self._val)
    
    def __float__(self):
        return float(self._val)
    
    def __hash__(self):
        return hash(float(self._val))
    
    def __floor__(self):
        return math.floot(self._val)
    
    def __ceil__(self):
        return math.ceil(self._val)
    
    def __round__(self, ndigits=None):
        return round(self._val, ndigits=ndigits)
    
    def __abs__(self):
        return abs(self._val)
    
    def __floordiv__(self, other):
        return self._val // other
    
    def __rfloordiv__(self, other):
        return other // self._val
    
    def __truediv__(self, other):
        return self._val / other
    
    def __rtruediv__(self, other):
        return other / self._val
    
    def __mod__(self, other):
        return self._val % other
        
    def __rmod__(self, other):
        return other % self._val
    
    def __lt__(self, other):
        return self._val < other
    
    def __le__(self, other):
        return self._val <= other
    
    def __eq__(self, other):
        return float(self) == float(other)
    
    def __neg__(self):
        return ExtendedInteger(-self.a, -self.b, self.p)
    
    def __pos__(self):
        return ExtendedInteger(+self.a, +self.b, self.p)
    
    def __add__(self, other):
        if isinstance(other, ExtendedInteger):
            # If both instances have the same p value,
            # return a new ExtendedInteger instance
            if self.p == other.p:
                new_a = self.a + other.a
                new_b = self.b + other.b
                return ExtendedInteger(new_a, new_b, self.p)
            # Otherwise return a float
            else:
                return self._val + other._val
        # If other is integral, add other to self's a value
        elif isinstance(other, numbers.Integral):
            new_a = self.a + other
            return ExtendedInteger(new_a, self.b, self.p)
        # If other is real, return a float
        elif isinstance(other, numbers.Real):
            return self._val + other._val
        # If other is of unknown type, let other determine
        # what to do
        else:
            return NotImplemented
    
    def __radd__(self, other):
        # Addition is commutative so defer to __add__
        return self.__add__(other)
    
    def __mul__(self, other):
        if isinstance(other, ExtendedInteger):
            # If both instances have the same p value,
            # return a new ExtendedInteger instance
            if self.p == other.p:
                new_a = (self.a * other.a) + (self.b * other.b * self.p)
                new_b = (self.a * other.b) + (self.b * other.a)
                return ExtendedInteger(new_a, new_b, self.p)
            # Otherwise, return a float
            else:
                return self._val * other._val
        # If other is integral, multiply self's a and b by other
        elif isinstance(other, numbers.Integral):
            new_a = self.a * other
            new_b = self.b * other
            return ExtendedInteger(new_a, new_b, self.p)
        # If other is real, return a float
        elif isinstance(other, numbers.Real):
            return self._val * other
        # If other is of unknown type, let other determine
        # what to do
        else:
            return NotImplemented
    
    def __rmul__(self, other):
        # Multiplication is commutative so defer to __mul__
        return self.__mul__(other)
    
    def __pow__(self, exponent):
        return self._val ** exponent
    
    def __rpow__(self, base):
        return base ** self._val

The number types defined above can be used as follows:

>>> a = ExtendedInteger(1, 2)
>>> b = ExtendedInteger(2, 3)

>>> a
ExtendedInteger(1, 2, 2)

>>> # Check that a is a Number
>>> isinstance(a, numbers.Number)
True

>>> # Check that a is Real
>>> isinstance(a, numbers.Real)
True

>>> print(a)
1 + 2√2

>>> a * b
ExtendedInteger(14, 7, 2)

>>> print(a * b)
14 + 7√2

>>> float(a)
3.8284271247461903

The inheritance function in Python allows us to define various objects flexibly.

conclusion

From the above, we can see that the numbers in Python can still be studied in depth.

reference material

[1]. David Amos, 3 Things You Might Not Know About Numbers in Python

[2]. Qi Wei, practical course of Python University, Beijing: Electronic Industry Press

Added by Angus on Thu, 27 Jan 2022 11:11:12 +0200