«Annuler» un bouclage entier


20

J'ai rencontré un problème théorique intéressant il y a plusieurs années. Je n'ai jamais trouvé de solution et elle continue de me hanter quand je dors.

Supposons que vous ayez une application (C #) contenant un certain nombre dans un entier, appelé x. (La valeur de x n'est pas fixe). Lorsque le programme est exécuté, x est multiplié par 33, puis écrit dans un fichier.

Le code source de base ressemble à ceci:

int x = getSomeInt();
x = x * 33;
file.WriteLine(x); // Writes x to the file in decimal format

Quelques années plus tard, vous découvrez que vous avez besoin des valeurs d'origine de X en arrière. Certains calculs sont simples: divisez simplement le nombre dans le fichier par 33. Cependant, dans d'autres cas, X est suffisamment grand pour que la multiplication provoque un débordement d'entier. Selon les documents , C # tronquera les bits de poids fort jusqu'à ce que le nombre soit inférieur à int.MaxValue. Est-il possible, dans ce cas, soit:

  1. Récupérer X lui-même ou
  2. Récupérer une liste de valeurs possibles pour X?

Il me semble (bien que ma logique puisse certainement être erronée) qu'un ou les deux devraient être possibles, car le cas plus simple de l'addition fonctionne (Essentiellement, si vous ajoutez 10 à X et qu'il se termine, vous pouvez soustraire 10 et finir avec X à nouveau ) et la multiplication est simplement une addition répétée. Il est également utile (je crois) que X soit multiplié par la même valeur dans tous les cas - une constante de 33.

Cela dansait autour de mon crâne à des moments étranges depuis des années. Cela me viendra à l'esprit, je passerai un peu de temps à y réfléchir, puis je l'oublierai pendant quelques mois. J'en ai assez de courir après ce problème! Quelqu'un peut-il offrir un aperçu?

(Note latérale: je ne sais vraiment pas comment marquer celui-ci. Suggestions bienvenues.)

Edit: Permettez-moi de préciser que si je peux obtenir une liste de valeurs possibles pour X, il y a d'autres tests que je pourrais faire pour m'aider à le réduire à la valeur d'origine.



1
@rwong: votre commentaire est la seule bonne réponse.
Kevin Cline

Oui, et la méthode d'Euler semble particulièrement efficace puisque la factorisation de mn'est que de 2 ^ 32 ou 2 ^ 64, plus l'exponentiation de amodulo mest simple (il suffit d'ignorer le débordement là-bas)
MSalters

1
Je pense que le problème particulier est en fait Rational Reconstruction
MSalters

1
@MSalters: Non, c'est là que vous avez r*s^-1 mod met vous devez trouver les deux ret s. Ici, nous avons r*s mod met nous savons tout, mais r.
user2357112 prend en charge Monica

Réponses:


50

Multipliez par 1041204193.

Lorsque le résultat d'une multiplication ne rentre pas dans un entier, vous n'obtiendrez pas le résultat exact, mais vous obtiendrez un nombre équivalent au résultat exact modulo 2 ** 32 . Cela signifie que si le nombre que vous multipliez était coprime à 2 ** 32 (ce qui signifie simplement qu'il doit être impair), vous pouvez multiplier par son inverse multiplicatif pour récupérer votre nombre. Wolfram Alpha ou l' algorithme euclidien étendu peut nous dire le modulo inverse multiplicatif de 2 ** 32 32 est 1041204193. Donc, multipliez par 1041204193, et vous avez le x d'origine.

Si nous avions, disons, 60 au lieu de 33, nous ne pourrions pas récupérer le nombre d'origine, mais nous pourrions le réduire à quelques possibilités. En factorisant 60 en 4 * 15, en calculant l'inverse de 15 mod 2 ** 32, et en multipliant par cela, nous pouvons récupérer 4 fois le nombre d'origine, ne laissant que 2 bits de poids fort du nombre à la force brute. Wolfram Alpha nous donne 4008636143 pour l'inverse, qui ne rentre pas dans un int, mais ça va. Nous trouvons juste un nombre équivalent à 4008636143 mod 2 ** 32, ou le forçons dans un int de toute façon pour que le compilateur le fasse pour nous, et le résultat sera également un inverse de 15 mod 2 ** 32. ( Nous obtenons -286331153. )


5
Oh mec. Donc tout le travail que mon ordinateur a fait pour construire la carte a déjà été fait par Euclid.
v010dya

21
J'aime la réalité dans votre première phrase. "Oh, c'est 1041204193, bien sûr. Tu ne l'as pas mémorisé?" :-P
Doorknob

2
Il serait utile de montrer un exemple de ce fonctionnement pour quelques nombres, comme celui où x * 33 n'a pas débordé et l'autre où il l'a fait.
Rob Watts

2
L'esprit soufflé. Sensationnel.
Michael Gazonda

4
Vous n'avez pas besoin d'Euclide ni de WolframAlpha (certainement!) Pour trouver l'inverse de 33 modulo $ 2 ^ {32} $. Puisque $ x = 32 = 2 ^ 5 $ est nilpotent (de l'ordre de 7 $) modulo $ 2 ^ 32 $, vous pouvez simplement appliquer l'identité de la série géométrique $ (1 + x) ^ {- 1} = 1-x + x ^ 2-x ^ 3 + \ cdots + x ^ 6 $ (après quoi la série s'arrête) pour trouver le nombre $ 33 ^ {- 1} = 1-2 ^ 5 + 2 ^ {10} -2 ^ {15} + \ cdots + 2 ^ {30} $ qui est $ 111110000011111000001111100001_2 = 1041204193_ {10} $.
Marc van Leeuwen

6

Cela peut être mieux adapté comme question à Math (sic) SE. Vous avez essentiellement affaire à l'arithmétique modulaire, car la suppression des bits les plus à gauche est la même chose.

Je ne suis pas aussi bon en maths que les gens qui sont en maths (sic) SE, mais je vais essayer de répondre.

Ce que nous avons ici, c'est que le nombre est multiplié par 33 (3 * 11), et son seul dénominateur commun avec votre mod est 1. C'est parce que, par définition, les bits de l'ordinateur sont des puissances de deux, et donc votre mod est une puissance de deux.

Vous pourrez construire la table où pour chaque valeur précédente vous calculez la valeur suivante. Et la question devient: les chiffres suivants correspondent-ils à un seul précédent?

Si ce n'était pas 33, mais un nombre premier ou une puissance d'un nombre premier, je pense que la réponse serait oui, mais dans ce cas… demandez à Math.SE!

Test programmatique

C'est en C ++ parce que je ne connais pas C #, mais le concept tient toujours. Cela semble montrer que vous pouvez:

#include <iostream>
#include <map>

int main(void)
{
    unsigned short count = 0;
    unsigned short x = 0;
    std::map<unsigned short, unsigned short> nextprev;

    nextprev[0] = 0;
    while(++x) nextprev[x] = 0;

    unsigned short nextX;
    while(++x)
    {
            nextX = x*33;
            if(nextprev[nextX])
            {
                    std::cout << nextprev[nextX] << "*33==" << nextX << " && " << x << "*33==" << nextX << std::endl;
                    ++count;
            }
            else
            {
                    nextprev[nextX] = x;
                    //std::cout << x << "*33==" << nextX << std::endl;
            }
    }

    std::cout << count << " collisions found" << std::endl;

    return 0;
}

Après avoir rempli une telle carte, vous pourrez toujours obtenir le X précédent si vous connaissez le suivant. Il n'y a qu'une seule valeur à tout moment.


Pourquoi travailler avec un type de données non négatif serait-il plus facile? Les signés et les non signés ne sont-ils pas traités de la même manière dans l'ordinateur, seul leur format de sortie humain diffère?
Xcelled

@ Xcelled194 Eh bien, il m'est plus facile de penser à ces chiffres.
v010dya

Assez juste xD Le facteur humain ~
Xcelled

J'ai supprimé cette déclaration de non négatif pour la rendre plus évidente.
v010dya

1
@ Xcelled194: Les types de données non signés suivent les règles habituelles de l'arithmétique modulaire; les types signés ne le font pas. En particulier, maxval+1est 0 uniquement pour les types non signés.
MSalters

2

Une façon de l'obtenir est d'utiliser la force brute. Désolé, je ne connais pas C # mais ce qui suit est un pseudo-code de type c pour illustrer la solution:

for (x=0; x<=INT_MAX; x++) {
    if (x*33 == test_value) {
        printf("%d\n", x);
    }
}

Techniquement, ce dont vous avez besoin est x*33%(INT_MAX+1) == test_valuemais le débordement d'entier fera automatiquement l' %opération pour vous à moins que votre langue n'utilise des entiers de précision arbitraire (bigint).

Ce que cela vous donne, c'est une série de chiffres qui pourraient être le numéro d'origine. Le premier nombre imprimé serait le nombre qui générerait un tour de débordement. Le deuxième nombre serait le nombre qui générerait deux tours de débordement. Etc..

Donc, si vous connaissez mieux vos données, vous pouvez faire une meilleure estimation. Par exemple, les calculs d'horloge courants (débordement toutes les 12 heures) ont tendance à rendre le premier nombre plus probable, car la plupart des gens s'intéressent aux événements qui se sont produits aujourd'hui.


C # se comporte comme C avec les types de base - c'est-à-dire intun entier signé de 4 octets qui encapsule, donc votre réponse est toujours bonne, bien que le forçage brut ne soit pas la meilleure façon de procéder si vous avez beaucoup d'entrées! :)
Xcelled

Oui, j'ai essayé de le faire sur papier avec des règles d'algèbre modulo d'ici: math.stackexchange.com/questions/346271/… . Mais je suis resté coincé à essayer de le comprendre et je me suis retrouvé avec une solution de force brute :)
slebetman

Article intéressant, même si je vais devoir l'étudier un peu plus en profondeur pour qu'il clique, je pense.
Xcelled

@slebetman Regardez mon code. Il semble qu'il n'y ait qu'une seule réponse quand il s'agit de multiplier par 33.
v010dya

2
Correction: il intn'est pas garanti que C se termine (voir les documents de votre compilateur). C'est vrai pour les types non signés.
Thomas Eding

1

Vous pouvez le solveur SMT Z3 pour lui demander de vous donner une affectation satisfaisante pour la formule x * 33 = valueFromFile. Il inversera cette équation pour vous et vous donnera toutes les valeurs possibles de x. Z3 prend en charge l'arithmétique bitvector exacte, y compris la multiplication.

    public static void InvertMultiplication()
    {
        int multiplicationResult = new Random().Next();
        int knownFactor = 33;

        using (var context = new Context(new Dictionary<string, string>() { { "MODEL", "true" } }))
        {
            uint bitvectorSize = 32;
            var xExpr = context.MkBVConst("x", bitvectorSize);
            var yExpr = context.MkBVConst("y", bitvectorSize);
            var mulExpr = context.MkBVMul(xExpr, yExpr);
            var eqResultExpr = context.MkEq(mulExpr, context.MkBV(multiplicationResult, bitvectorSize));
            var eqXExpr = context.MkEq(xExpr, context.MkBV(knownFactor, bitvectorSize));

            var solver = context.MkSimpleSolver();
            solver.Assert(eqResultExpr);
            solver.Assert(eqXExpr);

            var status = solver.Check();
            Console.WriteLine(status);
            if (status == Status.SATISFIABLE)
            {
                Console.WriteLine(solver.Model);
                Console.WriteLine("{0} * {1} = {2}", solver.Model.Eval(xExpr), solver.Model.Eval(yExpr), solver.Model.Eval(mulExpr));
            }
        }
    }

La sortie ressemble à ceci:

SATISFIABLE
(define-fun y () (_ BitVec 32)
  #xa33fec22)
(define-fun x () (_ BitVec 32)
  #x00000021)
33 * 2738875426 = 188575842

0

Annuler ce résultat vous donnera un nombre fini non nul de nombres (normalement infini, mais intest un sous-ensemble fini de ℤ). Si cela est acceptable, générez simplement les chiffres (voir les autres réponses).

Sinon, vous devez conserver une liste de l'historique (de longueur finie ou infinie) de l'historique de la variable.


0

Comme toujours, il y a une solution d'un scientifique et une solution d'un ingénieur.

Ci-dessus, vous trouverez une très bonne solution d'un scientifique, qui fonctionne toujours, mais vous oblige à calculer «l'inverse multiplicatif».

Voici une solution rapide de l'ingénieur, qui ne vous obligera pas à essayer tous les entiers possibles.

val multiplier = 33 //used with 0x23456789
val problemAsLong = (-1947051863).toLong & 0xFFFFFFFFL

val overflowBit = 0x100000000L
for(test <- 0 until multiplier) {
  if((problemAsLong + overflowBit * test) % multiplier == 0) {
    val originalLong = (problemAsLong + overflowBit * test) / multiplier
    val original = originalLong.toInt
    println(s"$original (test = $test)")
  }
}

Quelles sont les idées?

  1. Nous avons eu un débordement, utilisons donc des types plus grands pour récupérer ( Int -> Long)
  2. Nous avons probablement perdu quelques bits en raison d'un débordement, récupérons-les
  3. Le débordement n'était pas supérieur à Int.MaxValue * multiplier

Le code exécutable complet se trouve sur http://ideone.com/zVMbGV

Détails:

  • val problemAsLong = (-1947051863).toLong & 0xFFFFFFFFL
    Ici, nous convertissons notre numéro stocké en Long, mais comme Int et Long sont signés, nous devons le faire correctement.
    Nous limitons donc le nombre en utilisant ET au niveau du bit avec des bits de Int.
  • val overflowBit = 0x100000000L
    Ce bit ou sa multiplication pourrait être perdu par la multiplication initiale.
    C'est un premier peu en dehors de la plage Int.
  • for(test <- 0 until multiplier)
    Selon 3rd Idea, le débordement maximal est limité par le multiplicateur, alors n'essayez pas plus que ce dont nous avons vraiment besoin.
  • if((problemAsLong + overflowBit * test) % multiplier == 0)
    Vérifiez si en ajoutant un débordement éventuellement perdu, nous arrivons à une solution
  • val original = originalLong.toInt
    Le problème d'origine était dans la plage Int, alors revenons-y. Sinon, nous pourrions récupérer de manière incorrecte des nombres, qui étaient négatifs.
  • println(s"$original (test = $test)")
    Ne vous cassez pas après la première solution, car il pourrait y avoir d'autres solutions possibles.

PS: la 3e idée n'est pas strictement correcte, mais laissée pour être compréhensible.
Int.MaxValueest 0x7FFFFFFF, mais le débordement maximal est 0xFFFFFFFF * multiplier.
Le texte correct serait donc "Le débordement n'était pas supérieur à -1 * multiplier".
C'est exact, mais tout le monde ne le comprendra pas.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.