<jupyter_start><jupyter_text>Integers<jupyter_code>import sys
import time
# Integers are objects: instances of int class

print(type(10))
# Creating a integer object requires an overhaed of 24 bytes.
sys.getsizeof(0)
# The number 1 required 4 bytes.
sys.getsizeof(1)
# Larger number will require more storage space.
# Large integers will also slow down calculations.
print(sys.getsizeof(2**10000))
%time 2**10000 - 2**1000
%time 2**1000000 - 2**10000
print('===============')<jupyter_output>1360
CPU times: user 15 µs, sys: 1e+03 ns, total: 16 µs
Wall time: 18.1 µs
CPU times: user 2.69 ms, sys: 0 ns, total: 2.69 ms
Wall time: 2.61 ms
===============<jupyter_text>Integer operations Addition, subtraction, multiplication and exponentiation of integers always result in an integer.But the standard division operator / always results in a float value.<jupyter_code>print(type(2 + 3))
print(type(2 - 3))
print(type(2 * 3))
print(type(2 / 3))<jupyter_output><class 'int'>
<class 'int'>
<class 'int'>
<class 'float'><jupyter_text>For non-negative values (>= 0), the floor of the value is the same as the integer portion of the value (truncation)<jupyter_code>import math
print(math.floor(3.999))
print(math.trunc(3.999))
print(math.floor(-3.999))
print(math.trunc(-3.999))<jupyter_output>3
3
-4
-3<jupyter_text>The floor division operator a//b is the floor of a / bi.e. a // b = math.floor(a / b)This is true whether a and b are positive or negative.<jupyter_code>a = 33
b = 16
print(a/b)
print(a//b)
print(math.floor(a/b))
print('===================')<jupyter_output>2.0625
2
2
===================<jupyter_text>But this is not the case for negative numbers.<jupyter_code>a = -33
b = 16
print('{0}/{1} = {2}'.format(a, b, a/b))
print('trunc({0}/{1}) = {2}'.format(a, b, math.trunc(a/b)))
print('{0}//{1} = {2}'.format(a, b, a//b))
print('floor({0}//{1}) = {2}'.format(a, b, math.floor(a/b)))<jupyter_output>-33/16 = -2.0625
trunc(-33/16) = -2
-33//16 = -3
floor(-33//16) = -3<jupyter_text>The modulo operator and the floor division operator will always satisfy the following equation:a = b * (a // b) + a % b<jupyter_code>a = 13
b = 4
print(a == b * (a//b) + a%b)<jupyter_output>True<jupyter_text>Integers - Constructors and Bases<jupyter_code># The int class has two constructors
print(int(10))
# We can use the second constructor to 
# generate integers (base 10) from strings in any base.
print(int('101', 2))<jupyter_output>10
5<jupyter_text>Custom RebasingPython only provides built-in function to rebase to base 2, 8 and 16.For other bases, you have to provide your own algorithm or leverage some 3rd party library.<jupyter_code>def base10_to(n, base):
    """
    Convert a base 10 number n to another base.
    """
    if b < 2:
        raise ValueError('Base b must be >= 2')
    if n < 0:
        raise ValueError('Number n must be >= 0')
    if n == 0:
        return [0]
    digits = []
    while n > 0:
        m = n % base
        n = n // base
        digits.insert(0, m)
    return digits

def encode(digits, mapping):
    """
    Encode the digits into strings.
    """
    if max(digits) >= len(mapping):
        raise ValueError('digit_map is not long enough to encode digits')
    encoding = ''.join([mapping[d] for d in digits])
    return encoding

def rebase_from10(number, base):
    """
    Convert a based 10 number to a number in specified base.
    Param
        number: int
            A base 10 number.
        base: int
            The based you want to convert to.
    Return
        new_num: string
            The converted number.
    """
    mapping = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    if base < 2 or base > 36:
        raise ValueError('Invalid base: 2 <= base <= 36')
    sign = 1
    if number < 0:
        sign = -1
        number *= sign
    
    digits = base10_to(number, base)
    new_num = encode(digits, mapping)
    if sign == -1:
        new_num *= sign
    return new_num
rebase_from10(30, 16)<jupyter_output><empty_output><jupyter_text>Rational number<jupyter_code>from fractions import Fraction
import math
print(Fraction(1, 3)*3)
print(Fraction(2, 3) + Fraction(5,7))
# Since floats have finite precision, 
# any float can be converted to a rational number
import math
x = Fraction(math.pi)
print(x)
print(float(x))
# Float number does not always have an exact representation

Fraction(0.3)<jupyter_output><empty_output><jupyter_text>Python is trying to format the displayed value for readability - so it rounds the number for a better display format!<jupyter_code>format(0.3, '.20f')
delta = Fraction(0.3) - Fraction(3, 10)
delta == 0<jupyter_output><empty_output><jupyter_text>Floats  Internal Representation<jupyter_code>print(float(10))
print(float('0.5'))
# Floats do not always have an exact representation:
print(format(0.1, '.25f'))
# However, certain numbers can be represented exactly in a binary fraction expansion
print(format(0.125, '.25f'))<jupyter_output>0.1000000000000000055511151
0.1250000000000000000000000<jupyter_text>Equality TestingBecause not all real numbers have an exact float representation, equality testing can be tricky.<jupyter_code>x = 0.1 + 0.1 + 0.1
y = 0.3
x == y<jupyter_output><empty_output><jupyter_text>This is because 0.1 and 0.3 do not have exact representations.However, in some (limited) cases where all the numbers involved do have an exact representation<jupyter_code>x = 0.125 + 0.125 + 0.125
y = 0.375
x == y
# We can use a function isclose.
from math import isclose
x = 0.1 + 0.1 + 0.1
y = 0.3
isclose(x, y)<jupyter_output><empty_output><jupyter_text>The ``isclose`` method takes two optional parameters, ``rel_tol`` and ``abs_tol``.``rel_tol`` is a relative tolerance that will be relative to the magnitude of the largest of the two numbers being compared. Useful when we want to see if two numbers are close to each other as a percentage of their magnitudes.``abs_tol`` is an absolute tolerance that is independent of the magnitude of the numbers we are comparing - this is useful for numbers that are close to zero. In this situation we might consider x and y to be close to each other:<jupyter_code>x = 123456789.01
y = 123456789.02<jupyter_output><empty_output><jupyter_text>but not in this case:<jupyter_code>x = 0.01
y = 0.02
# We can combine the use of both relative and absolute tolerances in this way:

x = 0.0000001
y = 0.0000002

a = 123456789.01
b = 123456789.02

print('x = y:', isclose(x, y, abs_tol=0.0001, rel_tol=0.01))
print('a = b:', isclose(a, b, abs_tol=0.0001, rel_tol=0.01))<jupyter_output>x = y: True
a = b: True<jupyter_text>Coercing Floats to Integers  TruncationThe int class uses truncation when a float is passed in.<jupyter_code>from math import trunc
trunc(6.6)<jupyter_output><empty_output><jupyter_text>Floor<jupyter_code>from math import floor
print(floor(10.4), floor(10.5), floor(10.6))
print(floor(-10.4), floor(-10.5), floor(-10.6))<jupyter_output>10 10 10
-11 -11 -11<jupyter_text>Ceiling<jupyter_code>from math import ceil
print(ceil(10.4), ceil(10.5), ceil(10.6))
print(ceil(-10.4), ceil(-10.5), ceil(-10.6))<jupyter_output>11 11 11
-10 -10 -10<jupyter_text>RoundingHere, I want to talk about a special case: Ties.<jupyter_code>print(round(1.25, 1))
print(round(1.35, 1))
# Works similarly with n negative.
print(round(15, -1))
print(round(25, -1))<jupyter_output>1.2
1.4
20
20<jupyter_text>This is rounding to nearest, with ties to nearest number with even least significant digit, aka **Banker's Rounding**.  DecimalsDecimals have context, that can be used to specify rounding and precision (amongst other things)Contexts can be local (temporary contexts) or global (default)<jupyter_code>import decimal
from decimal import Decimal
# Precision
print(decimal.getcontext().prec)
# Rounding
print(decimal.getcontext().rounding)
# Use a local context manager.
x = Decimal('1.25')
y = Decimal('1.35')
print(round(x, 1), round(y, 1))
with decimal.localcontext() as ctx:
    ctx.rounding = decimal.ROUND_HALF_UP
    print(round(x, 1), round(y, 1))
print(round(x, 1), round(y, 1))<jupyter_output>1.2 1.4
1.3 1.4
1.2 1.4<jupyter_text>Constructors and Contexts<jupyter_code># Integers
Decimal(10)
# Strings
Decimal('.1')
# Tuples
Decimal ((0, (3,1,4,1,5), -4))
# Don't use float
Decimal(0.1)<jupyter_output><empty_output><jupyter_text>Math OperationsThe // and % operators (and consequently, the divmod() function) behave differently for integers and Decimals.In both cases the // and % operators satisfy the equation:n = d * (n // d) + (n % d)<jupyter_code># When both numbers are positive.
x = 10
y = 3
print(x//y, x%y)
print(divmod(x, y))
print( x == y * (x//y) + x % y)
print('=======Decimal=======')
a = Decimal('10')
b = Decimal('3')
print(a//b, a%b)
print(divmod(a, b))
print( a == b * (a//b) + a % b)
# When one number is negtive.
x = -10
y = 3
print(x//y, x%y)
print(divmod(x, y))
print( x == y * (x//y) + x % y)
print('=======Decimal=======')
a = Decimal('-10')
b = Decimal('3')
print(a//b, a%b)
print(divmod(a, b))
print( a == b * (a//b) + a % b)<jupyter_output>-4 2
(-4, 2)
True
=======Decimal=======
-3 -1
(Decimal('-3'), Decimal('-1'))
True<jupyter_text>You can use the math function of the math module, be aware that the math module functions will cast the Decimal numbers to floats when it performs the various operations.  Performance Considerations<jupyter_code>a = 3.1415
b = Decimal('3.1415')
print(sys.getsizeof(a))
print(sys.getsizeof(b))<jupyter_output>24
104<jupyter_text>Decimals take up a lot more memory than floats.Decimal arithmetic is also much slower than float arithmetic  Boolean<jupyter_code># The bool class inherits from the int class.
issubclass(bool, int)<jupyter_output><empty_output><jupyter_text>Since True and False are singletons, we can use either the is operator, or the == operator to compare them to any boolean expression.<jupyter_code>id(False), id(1 == 3)
# The interger values of True and False
int(True), int(False)<jupyter_output><empty_output><jupyter_text>Truth Values<jupyter_code># bool all value returns True except 0.
print(bool(0), bool(1), bool(5), bool(-1), bool(-5))<jupyter_output>False True True True True<jupyter_text>Boolean Operators<jupyter_code># or
# X or Y: If X is falsy, returns Y, otherwise evaluates and returns X
print('' or 'abc')
print(0 or 100)
print([] or [1, 2, 3])
# and
# X and Y: If X is falsy, returns X, otherwise evaluates and returns Y
s1 = None
s2 = ''
s3 = 'abc'
print(s1 and s1[0])
print(s2 and s2[0])
print(s3 and s3[0])<jupyter_output>None

a
{'fraction_non_alphanumeric': 0.13860252004581902, 'fraction_numerical': 0.05660557464681176, 'mean_word_length': 4.511309836927933, 'pattern_counts': {'":': 0, '<': 102, '<?xml version=': 0, '>': 102, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '27', 'dir': 'jupyter-structured-clean-dedup', 'id': '447352', 'license': 'MIT', 'n_tokens_mistral': 4097, 'n_tokens_neox': 3678, 'n_words': 1194, 'path': 'deep_dive/1_functional/1_numeric_types.ipynb', 'repo_name': 'UpcaseM/Learning_PY_hardway', 'size': '10476'}