web-dev-qa-db-fra.com

Conjecture Collatz Python - Sortie incorrecte supérieure à 2 billions (seulement!)

J'ai écrit un script de base en Python3 qui calcule la conjecture Collatz. Il prend un entier positif en entrée et renvoie le nombre de pas jusqu'à ce que la séquence descende à 1.

Mon script fonctionne parfaitement pour toutes les entrées entières inférieures à ~ 2 billions , mais au-dessus de ce seuil, les sorties sont trop petites.

À titre d'exemple, voici quelques entrées, la sortie de mon script et la sortie correcte réelle:

Integer Input          Script Output     Correct Output
   989,345,275,647        1,348             1,348 
 1,122,382,791,663        1,356             1,356 
 1,444,338,092,271        1,408             1,408 
 1,899,148,184,679        1,411             1,411 
 2,081,751,768,559          385             1,437 
 2,775,669,024,745          388             1,440 
 3,700,892,032,993          391             1,443 
 3,743,559,068,799          497             1,549 `

Les valeurs de sortie correctes sont basées sur ce lien: http://www.ericr.nl/wondrous/delrecs.html

La sortie de mon script est toujours exactement 1 052 inférieure à la sortie correcte pour les entrées supérieures à 2 billions, mais je n'ai aucune idée de la cause de cela.

Quelqu'un peut-il expliquer ce qui ne va pas et comment mettre à jour/corriger le script afin qu'il fonctionne correctement pour toutes les entrées? Je pensais que Python est capable d'accepter arbitrairement de grands nombres sans problème ...

Merci!

# Python Code for the Collatz Conjecture
# Rules: Take any integer 'n' and assess:
# If integer is even, divide by 2 (n/2)
# If integer is odd, multiply by 3 and add 1 (3n+1)
# Result: a list of all steps until 'n' goes down to 1

while True:
    print("Please enter a positive integer:")
    n = input("")
    if n == 'q':
        print("Until next time ...\n")
        break
    try:
        n = int(n)
        if n > 0:
            i = 0
            while n > 1:
                if n % 2 == 0:
                    n = int(n/2)
                    i += 1
                else:
                    n = int((3*n)+1)
                    i += 1
            print("# of steps to reach '1' = ", str(i), "\n")
        else:
            print("Sorry, that's not a valid entry. Please try again!\n")
    except ValueError:
        print("Sorry, that's not a valid entry. Please try again!\n")
38
ronster1000

Cette ligne:

n = int(n/2)

… Convertit n en float, divise ce float par 2, puis reconvertit en int en jetant la partie fractionnaire.

Pour les nombres entiers jusqu'à 2**52, La conversion en float est sans perte, mais pour tout ce qui est plus grand, elle doit être arrondie au nombre de 53 bits le plus proche, ce qui perd des informations.

Bien sûr, 2 billions sont bien en deçà de cette limite 2**53 Pour la précision du flottant - mais la séquence Collatz commençant à N va souvent beaucoup, beaucoup plus haut que N. Il n'est pas du tout invraisemblable que de nombreux nombres autour de 2 billions aient des séquences qui vont passé 2**53, alors que très peu de chiffres en dessous le font. Il est même possible qu'une longue séquence de nombres commençant à exactement 2 000 milliards dépasse 2**53 Mais pas un seul chiffre en dessous. Mais je ne sais pas comment prouver une telle chose sans construire la séquence entière pour chaque nombre jusqu'à 2 000 milliards. (S'il y a une preuve, elle s'appuierait probablement fortement sur les preuves partielles existantes de la conjecture dans diverses conditions différentes, qui sont au-dessus de ma rémunération…)

Quoi qu'il en soit, la solution est simple: vous souhaitez utiliser la division entière:

n = n // 2

Voici un exemple à démontrer:

>>> n = 2**53 + 3
>>> n
9007199254740995
>>> int(n/2)
4503599627370498
>>> n//2
4503599627370497

Pour vérifier que cela se produit réellement dans votre code, essayez ceci:

def collatz(n):
    overflow = False
    i = 0
    while n > 1:
        if n > 2**53:
            overflow=True
        if n % 2 == 0:
            n = int(n/2)
            i += 1
        else:
            n = int((3*n)+1)
            i += 1
    return i, overflow

if __== '__main__':
    import sys
    for arg in sys.argv[1:]:
        num = int(arg.replace(',', ''))
        result, overflow = collatz(num)
        print(f'{arg:>30}: {result:10,} {overflow}')

Quand je lance ceci:

$ python3 collatz.py 989,345,275,647 1,122,382,791,663 1,444,338,092,271 1,899,148,184,679 2,081,751,768,559 2,775,669,024,745 3,700,892,032,993 3,743,559,068,799

… ça me donne:

           989,345,275,647:      1,348 False
         1,122,382,791,663:      1,356 False
         1,444,338,092,271:      1,408 False
         1,899,148,184,679:      1,411 False
         2,081,751,768,559:        385 True
         2,775,669,024,745:        388 True
         3,700,892,032,993:        391 True
         3,743,559,068,799:        497 True

Nous avons donc dépassé 2**53 Exactement dans les mêmes cas où nous avons obtenu la mauvaise réponse.

Et pour vérifier le correctif, changez la int(n/2) en n//2:

           989,345,275,647:      1,348 False
         1,122,382,791,663:      1,356 False
         1,444,338,092,271:      1,408 False
         1,899,148,184,679:      1,411 False
         2,081,751,768,559:      1,437 True
         2,775,669,024,745:      1,440 True
         3,700,892,032,993:      1,443 True
         3,743,559,068,799:      1,549 True

Alors, pourquoi est-il toujours hors du même montant?

Eh bien, c'est surtout une coïncidence des nombres spécifiques que vous utilisez.

Lorsque vous passez 2**53 Via 3n+1, Vous allez convertir soit le dernier bit, soit les 2 derniers bits, en 0, ce qui signifie que vous coupez normalement une grande partie de la chaîne et remplacez-le par seulement 1 ou 2 divisions. Mais il y aura évidemment quelques chiffres où la chaîne vers laquelle vous finirez par sauter est plus longue que la bonne. En fait, il ne m'a fallu que 3 essais pour en trouver un: 3,743,559,068,799,123 Devrait prendre 326 étapes, mais cela prend 370.

Je soupçonne (mais encore une fois, je ne peux même pas imaginer comment prouver) que de nombreux grands nombres se retrouveront dans cette même plage autour de 375, un peu plus courts à mesure qu'ils deviennent (logarithmiquement) plus gros. Pourquoi? Eh bien, il n'y a qu'autant de nombres que vous pouvez arrondir - et la plupart d'entre eux sont probablement en cycles les uns avec les autres, vous commencez à tronquer la division. Supposons donc que presque tous les nombres proches de 2**53 Ont une longueur de cycle d'arrondi d'un peu plus de 50, et que la plupart des nombres dans la plage de milliards atteignent cette plage de 2**53 En un peu plus de 300 étapes… puis la plupart d'entre eux vont finir vers 375. (Ces chiffres sont tirés de nulle part, bien sûr, mais vous pouvez faire une simulation de Monte Carlo pour voir à quelle distance de la réalité ils sont réellement…)

63
abarnert