"Desfazendo" um número inteiro envolvente


20

Encontrei um problema teórico interessante há alguns anos. Eu nunca encontrei uma solução e ela continua me assombrando quando durmo.

Suponha que você tenha um aplicativo (C #) que contém algum número em um int, chamado x. (O valor de x não é fixo). Quando o programa é executado, x é multiplicado por 33 e, em seguida, gravado em um arquivo.

O código-fonte básico é assim:

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

Alguns anos depois, você descobre que precisa dos valores originais de X de volta. Alguns cálculos são simples: basta dividir o número no arquivo por 33. No entanto, em outros casos, X é grande o suficiente para que a multiplicação cause um estouro de número inteiro. De acordo com os documentos , o C # truncará os bits de ordem superior até que o número seja menor que int.MaxValue. É possível, neste caso, para:

  1. Recupere o próprio X ou
  2. Recuperar uma lista de valores possíveis para X?

Parece-me (embora minha lógica possa certamente ser falha) que uma ou ambas as coisas sejam possíveis, uma vez que o caso mais simples de adição funciona (essencialmente, se você adicionar 10 a X e envolver, poderá subtrair 10 e terminar com X novamente ) e multiplicação é simplesmente adição repetida. Também acredito (acredito) é o fato de X ser multiplicado pelo mesmo valor em todos os casos - uma constante 33.

Isso vem dançando em volta do meu crânio em momentos estranhos há anos. Isso me ocorre, passarei algum tempo tentando pensar nisso e depois esquecerei por alguns meses. Estou cansado de perseguir esse problema! Alguém pode oferecer insights?

(Nota: eu realmente não sei como marcar este. Sugestões são bem-vindas.)

Edit: Deixe-me esclarecer que, se eu puder obter uma lista de possíveis valores para X, há outros testes que eu poderia fazer para me ajudar a reduzi-lo ao valor original.



1
@rwong: seu comentário é a única resposta correta.
Kevin cline

Yup, método e de Euler parece particularmente eficaz desde a fatoração de mé apenas 2 ^ 32 ou 2 ^ 64, mais a exponenciação de amodulo mé simples (apenas ignorá estouro lá)
MSalters

1
Acho que o problema em particular é de fato reconstrução racional
MSalters

1
@MSalters: Não, é onde você tem r*s^-1 mod me precisa encontrar ambos re s. Aqui, temos r*s mod me sabemos tudo, menos r.
User2357112 suporta Monica

Respostas:


50

Multiplique por 1041204193.

Quando o resultado de uma multiplicação não se encaixa em um int, você não obterá o resultado exato, mas obterá um número equivalente ao resultado exato módulo 2 ** 32 . Isso significa que, se o número pelo qual você multiplicou foi coprime para 2 ** 32 (o que significa que deve ser ímpar), você pode multiplicar pelo inverso multiplicativo para recuperar o número. O Wolfram Alpha ou o algoritmo euclidiano estendido pode nos dizer o módulo inverso multiplicativo 2 de 33 ** 32 é 1041204193. Portanto, multiplique por 1041204193 e você terá o x original de volta.

Se tivéssemos, digamos, 60 em vez de 33, não conseguiríamos recuperar o número original, mas poderíamos reduzi-lo a algumas possibilidades. Fatorando 60 em 4 * 15, computando o inverso de 15 mod 2 ** 32 e multiplicando por isso, podemos recuperar 4 vezes o número original, deixando apenas 2 bits de alta ordem do número em força bruta. O Wolfram Alpha nos fornece 4008636143 para o inverso, que não se encaixa em um int, mas tudo bem. Acabamos de encontrar um número equivalente a 4008636143 mod 2 ** 32 ou forçá-lo a um int de qualquer maneira para que o compilador faça isso por nós, e o resultado também será um inverso de 15 mod 2 ** 32. ( Recebemos -286331153. )


5
Oh garoto. Portanto, todo o trabalho que meu computador fez na construção do mapa já foi feito por Euclid.
v010dya

21
Eu gosto da realidade na sua primeira frase. "Oh, é 1041204193, é claro. Você não memorizou isso?" :-P
Maçaneta 23/07

2
Seria útil mostrar um exemplo disso funcionando para alguns números, como um em que x * 33 não transbordou e outro onde ocorreu.
23414 Rob Robts

2
Mente soprada. Uau.
Michael Gazonda

4
Você não precisa de Euclides nem WolframAlpha (certamente!) Para encontrar o inverso de 33 módulos $ 2 ^ {32} $. Como $ x = 32 = 2 ^ 5 $ é nilpotente (da ordem $ 7 $), módulo $ 2 ^ 32 $, é possível aplicar a identidade geométrica da série $ (1 + x) ^ {- 1} = 1-x + x ^ 2-x ^ 3 + \ cdots + x ^ 6 $ (após o qual a série termina) para encontrar o número $ 33 ^ {- 1} = 1-2 ^ 5 + 2 ^ {10} -2 ^ {15} + \ cdots + 2 ^ {30} $, que é $ 111110000011111000001111100001_2 = 1041204193_ {10} $.
Marc van Leeuwen

6

Isso talvez seja mais adequado como uma pergunta para o Math (sic) SE. Você está basicamente lidando com aritmética modular, pois soltar os bits mais à esquerda é a mesma coisa.

Eu não sou tão bom em matemática quanto as pessoas que estão no Math (sic) SE, mas tentarei responder.

O que temos aqui é que o número está sendo multiplicado por 33 (3 * 11) e seu único denominador comum com o seu mod é 1. Isso porque, por definição, os bits no computador são potências de dois e, portanto, seu mod é algum poder de dois.

Você poderá construir a tabela em que, para cada valor anterior, calcula o seguinte valor. E a pergunta passa a ser: os números a seguir correspondem apenas a um anterior.

Se não fosse 33, mas um primo ou algum poder de primo, acredito que a resposta seria sim, mas neste caso ... pergunte no Math.SE!

Teste programático

Isso está em C ++ porque eu não sei C #, mas o conceito ainda é válido. Isso parece mostrar que você pode:

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

Depois de preencher esse mapa, você sempre poderá obter o X anterior se souber o próximo. Há apenas um valor único o tempo todo.


Por que seria mais fácil trabalhar com um tipo de dados não negativo? Os computadores assinados e não assinados são tratados da mesma maneira no computador, apenas o formato de saída humano é diferente?
Xcelled

@ Xcelled194 Bem, é mais fácil para mim pensar nesses números.
v010dya

Justo o suficiente xD O fator humano ~
Xcelled

Eu removi essa declaração sobre não-negativa para torná-la mais óbvia.
v010dya

1
@ Xcelled194: Tipos de dados não assinados seguem as regras usuais da aritmética modular; tipos assinados não. Em particular, maxval+1é 0 apenas para tipos não assinados.
MSalters

2

Uma maneira de conseguir isso é usar força bruta. Desculpe, eu não sei c #, mas o seguinte é pseudo código c-like para ilustrar a solução:

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

Tecnicamente, o que você precisa é que o x*33%(INT_MAX+1) == test_valueexcesso de número inteiro faça a %operação automaticamente, a menos que seu idioma use números inteiros de precisão arbitrários (bigint).

O que isso oferece é uma série de números que podem ter sido o número original. O primeiro número impresso seria o número que geraria uma rodada de estouro. O segundo número seria o número que geraria duas rodadas de estouro. E assim por diante..

Portanto, se você conhece melhor os dados, pode adivinhar melhor. Por exemplo, a matemática do relógio comum (transborda a cada 12 horas) tende a tornar o primeiro número mais provável, já que a maioria das pessoas está interessada nas coisas que aconteceram hoje.


O C # se comporta como o C com tipos básicos - ou seja, inté um número inteiro assinado de 4 bytes que envolve, então sua resposta ainda é boa, embora a força bruta não seja o melhor caminho a percorrer se você tiver muitas entradas! :)
Xcelled

Sim, tentei fazê-lo no papel com regras de álgebra modular aqui: math.stackexchange.com/questions/346271/… . Mas eu fiquei preso tentando descobrir isso e acabou com uma solução de força bruta :)
slebetman

Artigo interessante, embora eu tenha que estudá-lo um pouco mais profundamente para clicar, eu acho.
Xcelled

@slebetman Veja o meu código. Parece que existe apenas uma única resposta quando se trata de multiplicar por 33.
v010dya

2
Correção: C intnão é garantido para contornar (consulte os documentos do seu compilador). É verdade para tipos não assinados.
Thomas Eding

1

Você pode resolver o SMT Z3 para solicitar uma atribuição satisfatória para a fórmula x * 33 = valueFromFile. Isso inverterá essa equação e fornecerá todos os valores possíveis de x. O Z3 suporta a aritmética exata do vetor de bits, incluindo a multiplicação.

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

A saída é assim:

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

0

Desfazer esse resultado fornecerá uma quantidade finita diferente de zero (normalmente infinito, mas inté um subconjunto finito de ℤ). Se isso é aceitável, apenas gere os números (veja outras respostas).

Caso contrário, você precisará manter uma lista do histórico (de comprimento finito ou infinito) do histórico da variável.


0

Como sempre, existe uma solução de um cientista e uma solução de um engenheiro.

Acima, você encontrará uma solução muito boa de um cientista, que funciona sempre, mas exige que você calcule a "inversa multiplicativa".

Aqui está uma solução rápida do engenheiro, que não o forçará a tentar todos os números possíveis.

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

Quais são as idéias?

  1. Ficamos sobrecarregados, então vamos usar tipos maiores para recuperar ( Int -> Long)
  2. Provavelmente perdemos alguns bits devido ao estouro, vamos recuperá-los
  3. O estouro não foi superior a Int.MaxValue * multiplier

O código executável completo está localizado em http://ideone.com/zVMbGV

Detalhes:

  • val problemAsLong = (-1947051863).toLong & 0xFFFFFFFFL
    Aqui, convertemos nosso número armazenado em Long, mas, como Int e Long são assinados, precisamos fazê-lo corretamente.
    Portanto, limitamos o número usando AND bit a bit com bits de Int.
  • val overflowBit = 0x100000000L
    Este bit ou multiplicação pode ser perdido pela multiplicação inicial.
    É um primeiro bit fora do intervalo Int.
  • for(test <- 0 until multiplier)
    De acordo com a 3rd Idea, o excesso máximo é limitado pelo multiplicador, portanto, não tente mais do que realmente precisamos.
  • if((problemAsLong + overflowBit * test) % multiplier == 0)
    Verifique se, adicionando um possível excesso de perda, chegamos a uma solução
  • val original = originalLong.toInt
    O problema original estava no intervalo Int, então vamos voltar a ele. Caso contrário, poderíamos recuperar incorretamente números negativos.
  • println(s"$original (test = $test)")
    Não quebre após a primeira solução, pois pode haver outras soluções possíveis.

PS: A 3ª ideia não é estritamente correta, mas deixada para ser compreensível.
Int.MaxValueé 0x7FFFFFFF, mas o estouro máximo é 0xFFFFFFFF * multiplier.
Portanto, o texto correto seria "O excesso não foi maior que -1 * multiplier".
Isso está correto, mas nem todo mundo vai entender.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.