# PREUD'HOMME BONTOUX Geoffrey - PeiP 12 - 2014/2015
# TP n°9 donné le 21/11/2014 - Exercices sur la représentation des nombres
# http://www.fil.univ-lille1.fr/~wegrzyno/portail/Info/Doc/HTML/seq6_binaire.html#exercices

import doctest

# Exercice Écritures

# Écrire en binaire le nombre n=2014.

# En calculant :

# 2014 = 2 × 1007 + 0
# 1007 = 2 × 503 + 1
# 503 = 2 × 251 + 1
# 251 = 2 × 125 + 1
# 125 = 2 × 62 + 1
# 62 = 2 × 31 + 0
# 31 = 2 × 15 + 1
# 15 = 2 × 7 + 1
# 7 = 2 × 3 + 1
# 3 = 2 × 1 + 1
# 1 = 2 × 0 + 1
# 0b11111011110

# Avec Python

# >>> bin(n)
# '0b11111011110'


# Déterminez les écritures octale et hexadécimale de ce nombre de deux façons
# différentes.

# En calculant

# 2014 = 8 × 251 + 6
# 251 = 8 × 31 + 3
# 31 = 8 × 3 + 7
# 3 = 8 × 0 + 3
# 0o3736

# Avec Python

# >>> oct(2014)
# '0o3736'

# En calculant

# 2014 = 16 × 125 + 14 # E
# 125 = 16 × 7 + 13 # D
# 7 = 16 × 0 + 7
# 0x7de

# Avec Python

# >>> hex(2014)
# '0x7de'


# Exercice Pair ou impair ?

# Comment reconnaître qu’un nombre entier est pair ou impair lorsqu’on dispose
# de son écriture binaire ?

# Le dernier chiffre binaire détermine si un nombre est pair ou non (les
# puissance de deux supérieures à 0 étant forcément paires, leur somme ne peut
# donner qu'un nombre pair)
# Le prélicat suivant permet de déterminer si un nombre est pair

def bin_est_pair(b):
    """
    Indique si le nombre binaire est pair
    bin → bool

    >>> bin_est_pair('0b11111011110')
    True
    >>> bin_est_pair('0b10101011')
    False
    """

    return b[-1] == '0'


# Exercice

# Quel est le plus grand nombre entier qu’on peut écrire en binaire avec
# 8 bits ?

# Comparez ces nombres avec 2^t pour t=8,32,64.

# 0b11111111 = 1 × 2^0 + 1 × 2^1 + 1 × 2^2 + 1 × 2^3 + 1 × 2^4 + 1 × 2^5 + 1 ×
# 2^6 + 1 × 2^7 = 2^8-1 = 255

# 32 bits ?

# 0b11111111111111111111111111111111
# = 1 × 2^0 + 1 × 2^1 + 1 × 2^2 + 1 × 2^3 + 1 × 2^4 + 1 × 2^5 + 1 × 2^6 + 1 ×
# 2^7 + 1 × 2^8 + 1 × 2^9 + 1 × 2^10 + 1 × 2^11 + 1 × 2^12 + 1 × 2^13 + 1 × 2^14
# + 1 × 2^15 + 1 × 2^16 + 1 × 2^17 + 1 × 2^18 + 1 × 2^19 + 1 × 2^20 + 1 × 2^21 +
# 1 × 2^22 + 1 × 2^23 + 1 × 2^24 + 1 × 2^25 + 1 × 2^26 + 1 × 2^27 + 1 × 2^28 + 1
# × 2^29 + 1 × 2^30 + 1 × 2^31
# = 2^16-1 = 4 294 967 295

# 64 bits ?

# 0b1111111111111111111111111111111111111111111111111111111111111111
# = 1 × 2^0 + 1 × 2^1 + 1 × 2^2 + 1 × 2^3 + 1 × 2^4 + 1 × 2^5 + 1 × 2^6 + 1 ×
# 2^7 + 1 × 2^8 + 1 × 2^9 + 1 × 2^10 + 1 × 2^11 + 1 × 2^12 + 1 × 2^13 + 1 × 2^14
# + 1 × 2^15 + 1 × 2^16 + 1 × 2^17 + 1 × 2^18 + 1 × 2^19 + 1 × 2^20 + 1 × 2^21 +
# 1 × 2^22 + 1 × 2^23 + 1 × 2^24 + 1 × 2^25 + 1 × 2^26 + 1 × 2^27 + 1 × 2^28 + 1
# × 2^29 + 1 × 2^30 + 1 × 2^31 + 1 × 2^32 + 1 × 2^33 + 1 × 2^34 + 1 × 2^35 + 1 ×
# 2^36 + 1 × 2^37 + 1 × 2^38 + 1 × 2^39 + 1 × 2^40 + 1 × 2^41 + 1 × 2^42 + 1 ×
# 2^43 + 1 × 2^44 + 1 × 2^45 + 1 × 2^46 + 1 × 2^47 + 1 × 2^48 + 1 × 2^49 + 1 ×
# 2^50 + 1 × 2^51 + 1 × 2^52 + 1 × 2^53 + 1 × 2^54 + 1 × 2^55 + 1 × 2^56 + 1 ×
# 2^57 + 1 × 2^58 + 1 × 2^59 + 1 × 2^60 + 1 × 2^61 + 1 × 2^62 + 1 × 2^63
# = 2^64-1 = 18 446 744 073 709 551 615


# Exercice fonction ``mon_bin``

# En suivant l’algorithme de conversion vu dans le cours, programmez une
# fonction que vous nommerez mon_bin qui agit exactement de la même façon que la
# fonction bin de Python. Concevez d’abord votre fonction pour des nombres
# positifs

def mon_bin(d):
    """
    Équivalent de la fonction native bin() définie sur les entiers positifs
    int → bin
    CU : int strictement positif

    >>> mon_bin(2014)
    '0b11111011110'
    >>> mon_bin(42)
    '0b101010'
    """

    assert(d > 0), "d doit être strictement positif"
    D = d
    r = ''
    while D >= 1:
        r = str(D % 2) + r
        D = D // 2
    return '0b' + r


# Observez les réponses fournies par votre fonction pour plusieurs nombres
# positifs, puis pour 0. Est-ce que tout est correct ?

# >>> mon_bin(2014)
# '0b11111011110'
# >>> mon_bin(42)
# '0b101010'
# >>> mon_bin(0)
# '0b'
# >>> mon_bin(-42)
# '0b'

# Chaque nombre ≤ 0 donné renverra '0b'. Cela est dû au fait qu'on vérifie que
# D ≥ 1. On devrait utiliser sa valeur absolue, soit vérifier que |D| ≥ 1.
# Cependant, on remarque que l'instruction D // 2 arrondit à l'entier inférieur,
# la fonction restera bloqué avec un D valant -1. On préfèrera prendre la valeur
# absolue de d au départ et rajouter un - au résultat, plutôt que de vérifier
# si l'arrondi doit être fait à l'entier supérieur ou inférieur à l'intérieur
# de la boucle, ce qui prendrait trop de temps.

# Étendez le domaine d’application de votre fonction au cas de 0 et des nombres
# négatifs.

def mon_bin2(d):
    """
    Équivalent de la fonction native bin()
    int → bin

    >>> mon_bin2(2014)
    '0b11111011110'
    >>> mon_bin2(42)
    '0b101010'
    >>> mon_bin2(0)
    '0b0'
    >>> mon_bin2(-42)
    '-0b101010'
    """

    D = abs(d)
    if d != 0:
        r = ''
        while D >= 1:
            r = str(D % 2) + r
            D = D // 2
    else:
        r = '0'
    return ('-' if d < 0 else '') + '0b' + r


# Exercice fonction ``bin_inv``

# En utilsant l’algorithme vu en cours, réalisez une fonction que vous nommerez
# bin_inv qui fait le travail inverse de la fonction bin, c’est-à-dire qui
# calcule l’entier correspondant à une chaîne de caractères décrivant cet entier
# en binaire.

def bin_inv(b):
    """
    Réalise le travail inverse de la fonction native bin()
    bin → int

    >>> bin_inv ('0b101111')
    47
    >>> bin_inv ('-0b101111')
    -47
    """

    B = b.replace('0b', '')
    if b[0] == '-':
        B = B.replace('-', '')
    d = 0
    if len(B) > 0:
        for i in range(-1, -len(B) - 1, -1):
            d += int(B[i]) * 2 ** (-i - 1)
    return -d if b[0] == '-' else d


# Exercice fonction ``mon_hex``

# Que faut-il changer à l’algorithme de l’écriture binaire d’un nombre pour
# calculer la représentation hexadécimale d’un nombre entier naturel non nul ?

# Il faut remplacer les divisions par deux par des divisions par 16, et
# remplacer les nombres au dessus de 10 par des lettres. On utilisera une
# autre fonction pour cela, elle pourra très probablement resservir

# Réalisez une fonction que vous nommerez mon_hex équivalente à la fonction hex.
# Commencez pour les nombres positifs, puis envisagez le cas des nombres
# négatifs.

def mon_hex_unique(d):
    """
    Équivalent de la fonction native hex() pour un seul caractère héxadécimal
    int → hex
    CU : 0 ≤ d < 16

    >>> mon_hex_unique(11)
    'b'
    >>> mon_hex_unique(8)
    '8'
    """

    assert(type(d) is int and d >= 0 and d < 16), ""
    return chr(97 + d - 10) if d >= 10 else str(d)


def mon_hex(d):
    """
    Équivalent de la fonction native hex()
    int → hex

    >>> mon_hex(47)
    '0x2f'
    >>> mon_hex(-47)
    '-0x2f'
    """

    D = abs(d)
    if d != 0:
        r = ''
        while D >= 1:
            r = mon_hex_unique(D % 16) + r
            D = D // 16
    else:
        r = '0'
    return ('-' if d < 0 else '') + '0x' + r

# Exercice fonction ``hex_inv``

# Réalisez la fonction hex_inv inverse de la fonction hex, c’est–à–dire la
# fonction qui, à partir d’une chaîne de donnant l’écriture hexadécimale d’un
# entier, calcule cet entier. Vous devez obtenir par exemple:


def hex_inv_unique(h):
    """
    Réalise le travail inverse de la fonction native hex() pour un seul
    caractère héxadécimal
    hex → int

    >>> hex_inv_unique('b')
    11
    >>> hex_inv_unique('8')
    8
    """

    return ord(h) - 97 + 10 if ord(h) >= 97 else int(h)


def hex_inv(h):
    """
    Réalise le travail inverse de la fonction native hex()
    hex → int

    >>> hex_inv ('0x2f')
    47
    >>> hex_inv ('-0x2f')
    -47
    """
    H = h.replace('0x', '')
    if h[0] == '-':
        H = H.replace('-', '')
    d = 0
    if len(H) > 0:
        for i in range(-1, -len(H) - 1, -1):
            d += hex_inv_unique(H[i]) * 16 ** (-i - 1)
    return -d if h[0] == '-' else d


# Exercice fonction ``bin_en_hex``

# Sans utiliser les fonctions hex et/ou bin (ni même mon_bin et/ou mon_hex),
# programmez une fonction nommée bin_en_hex qui convertit une chaîne de
# caractères représentant un nombre entier écrit en binaire en la chaîne
# hexadécimale représentant le même entier.

# Notez bien dans cet exemple que la valeur passée en argument à la fonction
# bin_en_hex est une chaîne de caractères.

def bin_en_hex(b):
    """
    Convertit un binaire en hexadécimal
    bin → hex

    >>> bin_en_hex('0b101111')
    '0x2f'
    >>> bin_en_hex('-0b101111')
    '-0x2f'
    """
    B = b.replace('0b', '')
    if b[0] == '-':
        B = B.replace('-', '')
    if len(B) > 0:
        r = ''
        for i in range(-1, -len(B) - 1, -4):
            p = 0
            # On coupe le binaire en partie de 4 chiffres
            for j in range(4):
                rang = i - j
                if -rang <= len(B):
                    p += int(B[rang]) * 2 ** (j)
                else:
                    p += 0
            r = mon_hex_unique(p) + r
    else:
        r = '0'
    return ('-' if b[0] == '-' else '') + '0x' + r


def tester():
    doctest.testmod(verbose=True)