"Annullamento" di un intero avvolgente


20

Ho incontrato un interessante problema teorico diversi anni fa. Non ho mai trovato una soluzione e continua a perseguitarmi quando dormo.

Supponiamo di avere un'applicazione (C #) che contiene un numero in un int, chiamato x. (Il valore di x non è fisso). Quando viene eseguito il programma, x viene moltiplicato per 33 e quindi scritto in un file.

Il codice sorgente di base è simile al seguente:

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

Alcuni anni dopo, scopri che hai bisogno dei valori originali di X indietro. Alcuni calcoli sono semplici: basta dividere il numero nel file per 33. Tuttavia, in altri casi, X è abbastanza grande da causare un overflow di numeri interi. Secondo i documenti , C # troncerà i bit di ordine superiore fino a quando il numero è inferiore a int.MaxValue. In questo caso è possibile:

  1. Recupera X stesso o
  2. Recupera un elenco di possibili valori per X?

Mi sembra (anche se la mia logica potrebbe certamente essere imperfetta) che uno o entrambi dovrebbero essere possibili, dal momento che il caso più semplice di addizione funziona (essenzialmente se aggiungi 10 a X e si avvolge, puoi sottrarre 10 e finire con X di nuovo ) e la moltiplicazione è semplicemente un'aggiunta ripetuta. Anche aiutare (credo) è il fatto che X sia moltiplicato per lo stesso valore in tutti i casi - una costante 33.

Questo ha ballato intorno al mio cranio in momenti strani per anni. Mi verrà in mente, passerò un po 'di tempo a cercare di pensarci e poi me ne dimenticherò per qualche mese. Sono stanco di inseguire questo problema! Qualcuno può offrire informazioni?

(Nota a margine: non so davvero come taggarlo. Suggerimenti ben accetti.)

Modifica: vorrei chiarire che se riesco a ottenere un elenco di possibili valori per X, ci sono altri test che potrei fare per aiutarmi a restringere il valore originale.


13
Qualcosa sulla
falsariga

1
@rwong: il tuo commento è l'unica risposta corretta.
Kevin Cline il

Sì, e il metodo di Euler sembra particolarmente efficace poiché la fattorizzazione di mè solo 2 ^ 32 o 2 ^ 64, inoltre l'esponenziazione del amodulo mè semplice (basta ignorare l'overflow lì)
MSalters

1
Penso che il problema particolare sia in effetti la Rational Reconstruction
MSalters,

1
@MSalters: No, è lì che hai r*s^-1 mod me devi trovare entrambi re s. Qui abbiamo r*s mod me sappiamo tutto tranne r.
user2357112 supporta Monica il

Risposte:


50

Moltiplicare per 1041204193.

Quando il risultato di una moltiplicazione non rientra in un int, non otterrai il risultato esatto, ma otterrai un numero equivalente al risultato esatto modulo 2 ** 32 . Ciò significa che se il numero moltiplicato per era coprimi a 2 ** 32 (il che significa solo che deve essere dispari), puoi moltiplicare per il suo inverso moltiplicativo per recuperare il tuo numero. Wolfram Alpha o l' algoritmo euclideo esteso possono dirci il modulo inverso moltiplicativo di 33 2 di 33 ** 32 è 1041204193. Quindi, moltiplica per 1041204193 e hai la x originale.

Se avessimo, diciamo, 60 invece di 33, non saremmo in grado di recuperare il numero originale, ma saremmo in grado di restringerlo a poche possibilità. Inserendo 60 in 4 * 15, calcolando l'inverso di 15 mod 2 ** 32 e moltiplicando per quello, possiamo recuperare 4 volte il numero originale, lasciando solo 2 bit di alto ordine del numero a forza bruta. Wolfram Alpha ci dà 4008636143 per l'inverso, che non rientra in un int, ma va bene. Troviamo solo un numero equivalente a 4008636143 mod 2 ** 32, o forzalo comunque in un int affinché il compilatore lo faccia per noi, e il risultato sarà anche un inverso di 15 mod 2 ** 32. ( Otteniamo -286331153. )


5
Oh ragazzo. Quindi tutto il lavoro svolto dal mio computer nella costruzione della mappa è stato già svolto da Euclid.
v010dya,

21
Mi piace la verità nella tua prima frase. "Oh, è 1041204193, ovviamente. Non lo hai memorizzato?" :-P
Doorknob,

2
Sarebbe utile mostrare un esempio di questo funzionamento per un paio di numeri, come uno in cui x * 33 non ha traboccato e uno in cui ha funzionato.
Rob Watts,

2
Sbalordire. Wow.
Michael Gazonda,

4
Non hai bisogno né di Euclide né di WolframAlpha (certamente!) Per trovare l'inverso di 33 modulo $ 2 ^ {32} $. Poiché $ x = 32 = 2 ^ 5 $ è nilpotente (dell'ordine di $ 7 $) modulo $ 2 ^ 32 $, puoi semplicemente applicare l'identità della serie geometrica $ (1 + x) ^ {- 1} = 1-x + x ^ 2-x ^ 3 + \ cdots + x ^ 6 $ (dopodiché la serie si interrompe) per trovare il numero $ 33 ^ {- 1} = 1-2 ^ 5 + 2 ^ {10} -2 ^ {15} + \ cdots + 2 ^ {30} $ che è $ 111110000011111000001111100001_2 = 1041204193_ {10} $.
Marc van Leeuwen,

6

Questo forse si adatta meglio a una domanda a Math (sic) SE. In pratica hai a che fare con l'aritmetica modulare, dal momento che far cadere i bit più a sinistra è la stessa cosa.

Non sono bravo in matematica come le persone che sono in matematica (sic) SE, ma cercherò di rispondere.

Quello che abbiamo qui è che il numero viene moltiplicato per 33 (3 * 11) e il suo unico comune denominatore con la tua mod è 1. Questo perché per definizione i bit nel computer sono potenze di due, e quindi la tua mod è un po 'di potenza di due.

Sarai in grado di costruire la tabella in cui per ogni valore precedente calcoli il seguente valore. E la domanda diventa se i seguenti numeri corrispondono solo a uno precedente.

Se non fosse 33, ma un primo o una parte del potere di un primo, credo che la risposta sarebbe sì, ma in questo caso ... chiedi su Math.SE!

Test programmatico

Questo è in C ++ perché non conosco C #, ma il concetto è ancora valido. Questo sembra dimostrare che puoi:

#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;
}

Dopo aver popolato una tale mappa, sarai sempre in grado di ottenere la X precedente se conosci la prossima. C'è sempre un solo valore in ogni momento.


Perché lavorare con un tipo di dati non negativo sarebbe più semplice? Non sono firmati e non firmati gestiti allo stesso modo nel computer, solo il loro formato di output umano differisce?
Xcelled,

@ Xcelled194 Beh, è ​​più facile per me pensare a questi numeri.
v010dya,

Abbastanza giusto xD Il fattore umano ~
Xcelled il

Ho rimosso questa affermazione sul non negativo per renderlo più ovvio.
v010dya,

1
@ Xcelled194: i tipi di dati senza segno seguono le solite regole dell'aritmetica modulare; i tipi firmati no. In particolare, maxval+1è 0 solo per i tipi senza segno.
MSalters il

2

Un modo per ottenerlo è usare la forza bruta. Spiacenti, non conosco C # ma quanto segue è un pseudo codice simile a c per illustrare la soluzione:

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

Tecnicamente, ciò di cui hai bisogno è x*33%(INT_MAX+1) == test_valueche l'overflow dei numeri interi eseguirà automaticamente l' %operazione per te, a meno che la tua lingua non utilizzi numeri interi di precisione arbitraria (bigint).

Ciò che ti dà è una serie di numeri che potrebbero essere stati il ​​numero originale. Il primo numero stampato sarebbe il numero che genererebbe un round di overflow. Il secondo numero sarebbe il numero che genererebbe due round di overflow. E così via..

Quindi, se conosci meglio i tuoi dati, puoi fare un'ipotesi migliore. Ad esempio, la matematica dell'orologio comune (overflow ogni 12 in punto) tende a rendere il primo numero più probabile poiché la maggior parte delle persone sono interessate alle cose che sono successe oggi.


C # si comporta come C con i tipi di base - ovvero intè un intero con segno a 4 byte che avvolge, quindi la tua risposta è ancora buona, anche se la forza bruta non sarebbe il modo migliore se hai molti input! :)
Xcelled,

Sì, ho provato a farlo su carta con le regole di algebra del modulo da qui: math.stackexchange.com/questions/346271/… . Ma mi sono bloccato cercando di capirlo e ho finito con una soluzione di forza bruta :)
Slebetman,

Articolo interessante, anche se dovrò studiarlo un po 'più in profondità per fare clic, credo.
Xcelled,

@slebetman Guarda il mio codice. Sembra che ci sia una sola risposta quando si tratta di moltiplicare per 33.
v010dya

2
Correzione: intnon è garantito il completamento di C (consultare i documenti del compilatore). È vero per i tipi senza segno.
Thomas Eding,

1

Potresti risolvere il problema con il risolutore SMT Z3 per darti un incarico soddisfacente per la formula x * 33 = valueFromFile. Invertirà quell'equazione per te e ti darà tutti i possibili valori di x. Z3 supporta l'esatta aritmetica bitvector inclusa la moltiplicazione.

    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));
            }
        }
    }

L'output è simile al seguente:

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

0

Annullare quel risultato ti darà una quantità finita diversa da zero (normalmente infinita, ma intè un sottoinsieme finito di ℤ). Se questo è accettabile, basta generare i numeri (vedi altre risposte).

Altrimenti è necessario mantenere un elenco di cronologia (di lunghezza finita o infinita) della cronologia della variabile.


0

Come sempre, c'è una soluzione da uno scienziato e una soluzione da un ingegnere.

Sopra troverai un'ottima soluzione di uno scienziato, che funziona sempre, ma richiede di calcolare "l'inverso moltiplicativo".

Ecco una rapida soluzione dell'ingegnere, che non ti costringerà a provare tutti i numeri interi possibili.

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)")
  }
}

Quali sono le idee?

  1. Abbiamo overflow, quindi usiamo tipi più grandi per recuperare ( Int -> Long)
  2. Probabilmente abbiamo perso alcuni bit a causa dell'overflow, recuperiamoli
  3. Lo straripamento non era più di Int.MaxValue * multiplier

Il codice eseguibile completo si trova su http://ideone.com/zVMbGV

Dettagli:

  • val problemAsLong = (-1947051863).toLong & 0xFFFFFFFFL
    Qui convertiamo il nostro numero memorizzato in Long, ma poiché Int e Long sono firmati, dobbiamo farlo correttamente.
    Quindi limitiamo il numero usando bit a bit AND con bit di Int.
  • val overflowBit = 0x100000000L
    Questo bit o la sua moltiplicazione potrebbero essere persi dalla moltiplicazione iniziale.
    È un po 'al di fuori della gamma Int.
  • for(test <- 0 until multiplier)
    Secondo la terza idea, l'overflow massimo è limitato dal moltiplicatore, quindi non provare più di quanto abbiamo realmente bisogno.
  • if((problemAsLong + overflowBit * test) % multiplier == 0)
    Verifica se aggiungendo eventualmente un overflow perso arriviamo a una soluzione
  • val original = originalLong.toInt
    Il problema originale era nell'intervallo Int, quindi torniamo su di esso. Altrimenti potremmo recuperare in modo errato numeri, che erano negativi.
  • println(s"$original (test = $test)")
    Non rompere dopo la prima soluzione, perché potrebbero esserci altre possibili soluzioni.

PS: la terza idea non è strettamente corretta, ma lasciata per essere comprensibile.
Int.MaxValueè 0x7FFFFFFF, ma è il massimo trabocco 0xFFFFFFFF * multiplier.
Quindi il testo corretto sarebbe "L'overflow non era più di -1 * multiplier".
Questo è corretto, ma non tutti lo capiranno.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.