"Deshacer" un entero envuelto

20

Me encontré con un problema teórico interesante hace varios años. Nunca encontré una solución, y continúa obsesionándome cuando duermo.

Supongamos que tiene una aplicación (C #) que contiene algún número en un int, llamado x. (El valor de x no es fijo). Cuando se ejecuta el programa, x se multiplica por 33 y luego se escribe en un archivo.

El código fuente básico se ve así:

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

Algunos años después, descubres que necesitas recuperar los valores originales de X. Algunos cálculos son simples: simplemente divida el número en el archivo por 33. Sin embargo, en otros casos, X es lo suficientemente grande como para que la multiplicación provoque un desbordamiento de enteros. Según los documentos , C # truncará los bits de orden superior hasta que el número sea menor que int.MaxValue. ¿Es posible, en este caso,:

  1. Recuperar X mismo o
  2. ¿Recuperar una lista de posibles valores para X?

Me parece (aunque mi lógica ciertamente podría ser defectuosa) que uno o ambos deberían ser posibles, ya que el caso más simple de suma funciona (Esencialmente, si agrega 10 a X y se ajusta, puede restar 10 y terminar con X nuevamente ) y la multiplicación es simplemente una suma repetida. También ayuda (creo) el hecho de que X se multiplica por el mismo valor en todos los casos, una constante 33.

Esto ha estado bailando alrededor de mi cráneo en momentos extraños durante años. Se me ocurrirá, pasaré un tiempo tratando de pensarlo, y luego me olvidaré de eso durante unos meses. Estoy cansado de perseguir este problema! ¿Alguien puede ofrecer una idea?

(Nota al margen: Realmente no sé cómo etiquetar este. Se aceptan sugerencias.)

Editar: Permítanme aclarar que si puedo obtener una lista de posibles valores para X, hay otras pruebas que podría hacer para ayudarme a reducirlo al valor original.

Xcelled
fuente
1
@rwong: tu comentario es la única respuesta correcta.
Kevin Cline
Sí, el método y de Euler parece particularmente eficaz ya que la factorización de mes sólo 2 ^ 32 o 2 ^ 64, además de la exponenciación de amódulo mes sencillo (simplemente ignorar desbordamiento allí)
MSalters
1
Creo que el problema particular es de hecho Reconstrucción racional
MSalters
1
@MSalters: No, ahí es donde tienes r*s^-1 mod my necesitas encontrar ambos ry s. Aquí tenemos r*s mod my sabemos todo menos r.
user2357112 es compatible con Monica el

Respuestas:

50

Multiplicar por 1041204193.

Cuando el resultado de una multiplicación no cabe en un int, no obtendrá el resultado exacto, pero obtendrá un número equivalente al resultado exacto módulo 2 ** 32 . Eso significa que si el número por el que multiplicaste fue coprimo a 2 ** 32 (lo que significa que tiene que ser impar), puedes multiplicar por su inverso multiplicativo para recuperar tu número. Wolfram Alpha o el algoritmo Euclidiano extendido pueden decirnos que el módulo inverso multiplicativo 2 33 de 33 es 1041204193. Entonces, multiplique por 1041204193, y tendrá la x original de vuelta.

Si tuviéramos, por ejemplo, 60 en lugar de 33, no podríamos recuperar el número original, pero podríamos reducirlo a unas pocas posibilidades. Al factorizar 60 en 4 * 15, calcular el inverso de 15 mod 2 ** 32, y multiplicar por eso, podemos recuperar 4 veces el número original, dejando solo 2 bits de alto orden del número a fuerza bruta. Wolfram Alpha nos da 4008636143 para el inverso, que no cabe en un int, pero está bien. Simplemente encontramos un número equivalente a 4008636143 mod 2 ** 32, o lo forzamos a un int de todos modos para que el compilador lo haga por nosotros, y el resultado también será un inverso de 15 mod 2 ** 32. ( Obtenemos -286331153. )

user2357112 es compatible con Monica
fuente
55
Oh chico. Así que Euclid ya hizo todo el trabajo que mi computadora realizó para construir el mapa.
v010dya
21
Me gusta la realidad en tu primera oración. "Oh, es 1041204193, por supuesto. ¿No lo has memorizado?" :-P
Pomo de la puerta
2
Sería útil mostrar un ejemplo de esto funcionando para un par de números, como uno donde x * 33 no se desbordó y otro donde sí.
Rob Watts
2
Mente alucinada. Guau.
Michael Gazonda
44
No necesita ni Euclid ni WolframAlpha (¡ciertamente!) Para encontrar el inverso de 33 módulo $ 2 ^ {32} $. Como $ x = 32 = 2 ^ 5 $ es nilpotente (de orden $ 7 $) módulo $ 2 ^ 32 $, puede aplicar la identidad de serie geométrica $ (1 + x) ^ {- 1} = 1-x + x ^ 2-x ^ 3 + \ cdots + x ^ 6 $ (después de lo cual se rompe la serie) para encontrar el número $ 33 ^ {- 1} = 1-2 ^ 5 + 2 ^ {10} -2 ^ {15} + \ cdots + 2 ^ {30} $ que es $ 111110000011111000001111100001_2 = 1041204193_ {10} $.
Marc van Leeuwen
6

Esto puede ser más adecuado como una pregunta para Math (sic) SE. Básicamente se trata de aritmética modular, ya que soltar los bits más a la izquierda es lo mismo.

No soy tan bueno en matemáticas como las personas que están en matemáticas (sic) SE, pero intentaré responder.

Lo que tenemos aquí es que el número se multiplica por 33 (3 * 11), y su único denominador común con su mod es 1. Eso es porque, por definición, los bits en la computadora son potencias de dos, y por lo tanto su mod es alguna potencia de dos.

Podrá construir la tabla donde para cada valor anterior calcule el siguiente valor. Y la pregunta es si los siguientes números corresponden a uno solo anterior.

Si no fuera 33, sino un primo o algún poder de un primo, creo que la respuesta sería sí, pero en este caso ... ¡pregunte por Math.SE!

Prueba programática

Esto está en C ++ porque no sé C #, pero el concepto aún se mantiene. Esto parece mostrar que puedes:

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

Después de llenar dicho mapa, siempre podrá obtener la X anterior si conoce la siguiente. Solo hay un valor único en todo momento.

v010dya
fuente
¿Por qué sería más fácil trabajar con un tipo de datos no negativo? ¿No se firman y se firman de la misma manera en la computadora, solo difiere su formato de salida humana?
Xcelled
@ Xcelled194 Bueno, es más fácil para mí pensar en estos números.
v010dya
Bastante justo xD El factor humano ~
Xcelled
He eliminado esa declaración sobre no negativo para hacerlo más obvio.
v010dya
1
@ Xcelled194: los tipos de datos sin firmar siguen las reglas habituales de la aritmética modular; los tipos con signo no. En particular, maxval+1es 0 solo para tipos sin signo.
MSalters
2

Una forma de obtenerlo es usar la fuerza bruta. Lo siento, no sé C #, pero el siguiente es un pseudocódigo similar a C para ilustrar la solución:

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

Técnicamente, lo que necesita es que el x*33%(INT_MAX+1) == test_valuedesbordamiento de enteros realice automáticamente la %operación por usted a menos que su idioma use enteros de precisión arbitraria (bigint).

Lo que esto te da es una serie de números que pueden haber sido el número original. El primer número impreso sería el número que generaría una ronda de desbordamiento. El segundo número sería el número que generaría dos rondas de desbordamiento. Y así..

Entonces, si conoce mejor sus datos, puede adivinar mejor. Por ejemplo, las matemáticas de reloj comunes (desbordamiento cada 12 en punto) tienden a hacer que el primer número sea más probable ya que la mayoría de las personas están interesadas en las cosas que sucedieron hoy.

slebetman
fuente
C # se comporta como C con tipos básicos, intes decir, es un entero con signo de 4 bytes que se ajusta, por lo que su respuesta sigue siendo buena, ¡aunque el forzado bruto no sería la mejor manera de hacerlo si tiene muchas entradas! :)
Xcelled
Sí, intenté hacerlo en papel con reglas de álgebra de módulo desde aquí: math.stackexchange.com/questions/346271/… . Pero me quedé atrapado tratando de resolverlo y terminé con una solución de fuerza bruta :)
slebetman
Artículo interesante, aunque creo que tendré que estudiarlo un poco más en profundidad para que haga clic.
Xcelled
@Slebetman Mira mi código. Parece que solo hay una respuesta única cuando se trata de multiplicar por 33.
v010dya
2
Corrección: intno se garantiza que C se ajuste (consulte los documentos de su compilador). Sin embargo, es cierto para los tipos sin signo.
Thomas Eding
1

Podría pedirle al solucionador de SMT Z3 que le pida una asignación satisfactoria para la fórmula x * 33 = valueFromFile. Invertirá esa ecuación para usted y le dará todos los valores posibles de x. Z3 admite la aritmética exacta del vector de bits, incluida la multiplicación.

    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 salida se ve así:

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

Deshacer ese resultado le dará una cantidad finita de números distintos de cero (normalmente infinito, pero intes un subconjunto finito de ℤ). Si esto es aceptable, solo genera los números (ver otras respuestas).

De lo contrario, debe mantener una lista del historial (de longitud finita o infinita) del historial de la variable.

Thomas Eding
fuente
0

Como siempre, hay una solución de un científico y una solución de un ingeniero.

Arriba encontrará una muy buena solución de un científico, que funciona siempre, pero requiere que calcule "inverso multiplicativo".

Aquí hay una solución rápida del ingeniero, que no lo obligará a probar todos los enteros posibles.

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

Cuales son las ideas?

  1. Tenemos desbordamiento, así que usemos tipos más grandes para recuperar ( Int -> Long)
  2. Probablemente perdimos algunos bits debido al desbordamiento, recuperemoslos
  3. El desbordamiento no fue más que Int.MaxValue * multiplier

El código ejecutable completo se encuentra en http://ideone.com/zVMbGV

Detalles:

  • val problemAsLong = (-1947051863).toLong & 0xFFFFFFFFL
    Aquí convertimos nuestro número almacenado a Long, pero como Int y Long están firmados, tenemos que hacerlo correctamente.
    Entonces, limitamos el número usando bit a bit Y con bits de Int.
  • val overflowBit = 0x100000000L
    Este bit o multiplicación podría perderse por la multiplicación inicial.
    Está un poco fuera del rango Int.
  • for(test <- 0 until multiplier)
    Según 3rd Idea, el desbordamiento máximo está limitado por el multiplicador, así que no intentes más de lo que realmente necesitamos.
  • if((problemAsLong + overflowBit * test) % multiplier == 0)
    Compruebe si al agregar un desbordamiento posiblemente perdido llegamos a una solución
  • val original = originalLong.toInt
    El problema original estaba en el rango Int, así que volvamos a él. De lo contrario, podríamos recuperar incorrectamente los números, que fueron negativos.
  • println(s"$original (test = $test)")
    No se rompa después de la primera solución, porque podría haber otras posibles soluciones.

PD: 3rd Idea no es estrictamente correcta, pero se dejó para ser entendible.
Int.MaxValuees 0x7FFFFFFF, pero el desbordamiento máximo es 0xFFFFFFFF * multiplier.
Por lo tanto, el texto correcto sería "El desbordamiento no fue más que -1 * multiplier".
Esto es correcto, pero no todos lo entenderán.

Oleg Rudenko
fuente