# Exercice 1

# En théorie, morris(n, whatever) = morris(n-1, whateverbis) = morris(n-2, whateverter) = ... = morris(0, ???) = 1.
# En pratique, avant d'exécuter une fonction il faut évaluer les arguments et le deuxième argument lance une récursion infinie car il s'appelle avec les mêmes paramètres.

# Exercice 2

# Terminaison : chaque appel récursif se fait avec un couple d'arguments strictement inférieur au couple de l'appel qui l'a déclenché
# (la terminaison pour les récursions se prouve essentiellement comme pour une boucle conditionnelle, on le verra au chapitre sur la récursion),
# en utilisant l'ordre lexicographique.
# On a directement ackermann(0, n) = n+1.
# Ensuite, ackermann(1, n) = ackermann(0, ackermann(1, n-1)) = ackermann(1, n-1) + 1 avec ackermann(1, 0) = ackermann(0, 1) = 2 d'où ackermann(1, n) = n + 2.
# Ensuite, ackermann(2, n) = ackermann(1, ackermann(2, n-1)) = ackermann(2, n-1) + 2 avec ackermann(2, 0) = ackermann(1, 1) = 3 d'où ackermann(2, n) = 2 * n + 3.
# Enfin, ackermann(3, n) = ackermann(2, ackermann(3, n-1)) = 2 * ackermann(3, n-1) + 3 avec ackermann(3, 0) = ackermann(2, 1) = 5
# d'où (suite arithmético-géométrique) ackermann(3, n) = 8 * 2 ** n - 3.
# Pour ackermann(4, n), on a une tour d'exponentielles de taille n+3 avec 2 à chaque endroit, ce qui dépasse rapidement les capacités de calcul d'un ordinateur,
# à plus forte raison la pile d'appels de Python. Pour information, ackermann(4, 2) a environ 20 000 chiffres.

# Exercice 3

x = -1+10**-100 # pas besoin de parenthéser entre ** et -, mais ce n'était pas évident
y = 1
z = -10**-100 # au passage, la notation -1e-100 est autorisée aussi (attention à la syntaxe)

print((x+y)+z) # même résultat que sans parenthèses
print(x+(y+z))

x = 2**52+1. # autre exemple, faisant intervenir les soucis d'arrondis avec les flottants
y = 2**52
z = -2**52

print((x+y)+z)
print(x+(y+z))

x = -4
y = 3
print(int(x/y)) # int est une troncature, pas la partie entière
print(x//y)

x = 10**99 - 1 # autre exemple, faisant intervenir les soucis d'arrondis avec les flottants
y = 10

print(int(x/y)) # encore plus spectaculaire que de demander à la console sans le print
print(x//y)

# Exercice 4

# Il s'avère que round sans deuxième argument arrondit à l'entier le plus proche, en choisissant l'entier pair en cas d'égalité.
# En France, la convention arrondit systématiquement par excès en cas d'égalité selon la plupart des personnes interrogées, de mon côté j'avais appris par défaut…
# En tout cas, avec un deuxième argument, un entier ici noté n, on obtient un flottant, l'arrondi se faisant n chiffres après la virgule (potentiellement avant…),
# selon les mêmes règles. Ceci étant, une égalité apparente comme avec 1.05 n'en est pas forcément une pour Python !

# Exercice 5

# Autant on peut s'attendre à un piège avec le retrait à trois reprises d'un tiers en partant de la valeur 1, autant pour 0.2 on constate avec plus de surprise
# qu'on n'obtient pas zéro. En fait même si 0.2 est décimal, il n'est pas dyadique, alors que 0.25 ne posera pas le moindre souci.

# Exercice 6

def discriminant(a, b, c):
    return b*b - 4*a*c

def solutions_trinome(a, b, c):
    d = discriminant(a, b, c)
    assert d >= 0, "Pas de solution"
    if d == 0:
        return [-b / (2*a)]
    return [(-b - d**.5)/(2*a), (-b + d**.5)/(2*a)]
    
# Trois erreurs possibles sont rencontrées : discriminant reconnu comme strictement négatif alors qu'il est censé être nul,
# puis reconnu comme strictement positif alors qu'il est censé être nul, puis reconnu comme nul alors qu'il est censé être strictement négatif.
# Dans le dernier cas, c'est un peu artificiel car 1/4 + 10**-20 est déjà arrondi à 0.25 avant le calcul du discriminant.
# Un cas possible où l'erreur peut intervenir dans le calcul du discriminant seulement : x^2 + (1 + 10**-10)x + (1 + 2*10**-10)/4.

# Exercice 7

# Le flottant qui approche 2**61 - 2 au plus près est 2**61, de même pour 2**61 - 3. Ainsi, l'entier 2**61 - 2 est strictement inférieur au flottant représentant 2**61 - 3.

# Exercice 8

# La non terminaison théorique vient du fait que le réel zéro, limite de la suite des valeurs prises par x n'est pas atteignable par les éléments de la suite.
# Ce sera la même remarque pour les autres exercices.

# On ajoute un compteur avant la boucle et on le met à jour pendant la boucle. Le nombre de tours s'obtiendra en demandant la valeur du compteur

x = 1
c = 0
while x != 0:
    x /= 2
    c += 1

# La valeur de c peut s'expliquer par les détails d'implémentation des nombres en virgule flottante, mais ces détails ne sont pas au programme.
# Pour les exercices suivants, cela relève du cours du chapitre 1.

# Exercice 9

# Il s'agit d'une moyenne arithmétique calculée successivement

x = 2
c = 0
while x != 1:
    x = (x + 1) / 2
    c += 1

# Exercice 10

# À ce stade, autant faire une fonction, qui pourrait par ailleurs rassembler les trois exercices.

def nombre_tours(limite):
    x = limite + 1
    c = 0
    while x != limite:
        x = (x + limite) / 2
        c += 1
    return c
