2012-07-28 14 views
5

dado una extraña long x, estoy buscando long y tal que su módulo de productos 2**64 (es decir, utilizando la aritmética desbordante normal) es igual a 1. Para poner en claro lo que quiero decir: Esto podría ser calculado en unos pocos miles de años de esta manera:Java inversa módulo 2 ** 64

for (long y=1; ; y+=2) { 
    if (x*y == 1) return y; 
} 

sé que esto puede ser resuelto rápidamente usando el algoritmo de Euclides extendido, pero se requiere la capacidad de representar todos los números involucrados (que van hasta 2**64, por lo que incluso sin firmar la aritmética no ayudaría). Usar BigInteger seguramente ayudaría, pero me pregunto si hay una manera más simple, posiblemente usando el algoritmo euclidiano extendido implementado para longs positivos.

+0

Hacker's Delight [sugiere] (http://www.hackersdelight.org/HDcode/mulinv.c.txt) un algoritmo para mod 2^32. Intentaré alguna variante de eso, aunque probaría mucho, por supuesto. (Tal vez podría valer la pena incluirlo en Guava ...) –

+0

@Louis Wasserman: Buen enlace ... mientras tanto, creo que tengo algo 3 veces más rápido, publicaré mis resultados más tarde. Por cierto, necesitaba 'pow (long, long)' (que falta en 'LongMath') para un enfoque. – maaartinus

+0

'pow (long, long)' está deliberadamente ausente porque llevar todo a una potencia que no encaja en un 'int' está esencialmente garantizado para desbordarse. (Aunque supongo que eso es lo que quiere en su caso.) –

Respuesta

1

Aquí hay una manera de hacerlo. Esto utiliza el algoritmo de Euclides extendido a encontrar una inversa de abs(x) módulo 2 , y al final se 'extiende' la respuesta hasta un módulo inversa 2 y aplica un cambio de signo si es necesario:

public static long longInverse(long x) { 

    if (x % 2 == 0) { throw new RuntimeException("must be odd"); } 

    long power = 1L << 62; 

    long a = Math.abs(x); 
    long b = power; 
    long sign = (x < 0) ? -1 : 1; 

    long c1 = 1; 
    long d1 = 0; 
    long c2 = 0; 
    long d2 = 1; 

    // Loop invariants: 
    // c1 * abs(x) + d1 * 2^62 = a 
    // c2 * abs(x) + d2 * 2^62 = b 

    while (b > 0) { 
     long q = a/b; 
     long r = a % b; 
     // r = a - qb. 

     long c3 = c1 - q*c2; 
     long d3 = d1 - q*d2; 

     // Now c3 * abs(x) + d3 * 2^62 = r, with 0 <= r < b. 

     c1 = c2; 
     d1 = d2; 
     c2 = c3; 
     d2 = d3; 
     a = b; 
     b = r; 
    } 

    if (a != 1) { throw new RuntimeException("gcd not 1 !"); } 

    // Extend from modulo 2^62 to modulo 2^64, and incorporate sign change 
    // if necessary. 
    for (int i = 0; i < 4; ++i) { 
     long possinv = sign * (c1 + (i * power)); 
     if (possinv * x == 1L) { return possinv; } 
    } 

    throw new RuntimeException("failed"); 
} 

me pareció más fácil trabajar con 2 de 2 , sobre todo porque evita problemas con los números negativos: 2 como Java long es negativo.

+0

Acepté su solución ya que su comentario al mío condujo a la más rápida, consulte [aquí] (https://dl.dropbox.com/u/4971686/published/maaartin/math/index.html). – maaartinus

1

Mientras tanto he recordado/reinventado una solución muy simple:

public static int inverseOf(int x) { 
    Preconditions.checkArgument((x&1)!=0, "Only odd numbers have an inverse, got " + x); 
    int y = 1; 
    for (int mask=2; mask!=0; mask<<=1) { 
     final int product = x * y; 
     final int delta = product & mask; 
     y |= delta; 
    } 
    return y; 
} 

Funciona debido a dos cosas:

  • en cada iteración si el bit correspondiente de product es 1, entonces está mal, y la única forma de arreglarlo es cambiando el bit correspondiente de y
  • ningún bit de y influye en cualquier bit menos significativo o f product, por lo que no se deshace el trabajo previo

Empecé con int ya que para long debe trabajar también, y para int que podría correr una prueba exhaustiva.

Otra idea: debe haber un número n>0 tal que x**n == 1, y por lo tanto y == x**(n-1). Esto probablemente debería ser más rápido, simplemente no puedo recordar suficientes operaciones matemáticas para calcular n.

+0

+1 respuesta interesante. Creo que el valor de 'n' que mencionas es' 2 ** 62'. –

+0

@Luke Woodward: Usando [la función totient de Euler] (http://en.wikipedia.org/wiki/Euler%27s_totient_function) obtengo '2 ** 63', pero parece funcionar con' 2 ** 62' como bien. Y parece conducir al método más rápido. – maaartinus