fac 5483217492187432 = 0

fac 5483217492187432 = 0

de Gabriel Emmanuel Mello Muñoz -
Número de respuestas: 1

Buenas, comienzo por aclarar que no hay ninguna pregunta en este post. Sólo vengo a comentar que entendí qué fue lo que pasó en la clase de práctico del viernes y en el momento no logramos explicar. 

Para mostrar las ventajas de la evaluación perezosa se planteó como ejemplo el caso de la función factorial

fac 0 = 1

fac n = n * fac (n-1)

A continuación se evaluó dicha función en un valor suficientemente grande como para que el tiempo de calcularlo fuera apreciable. Lo que observamos fue que el resultado de este cómputo fue 0, y dijimos "qué casualidad que justo cayó ahí al reducirlo mod n". La realidad es que esto no es casualidad si no que es una particularidad de la función factorial. 

Según la documentación de Haskell, "todas las operaciones aritméticas son realizadas módulo 2^n donde n es el número de bits en el tipo", y este n no necesariamente es 32 ni 64 ya que por cuestiones de implementación se pueden usar algunos bits para almacenar otro tipo de información. 

Esto implica que una vez que para un n_0, fac n_0 sea múltiplo de 2^n, para todo n mayor o igual a ese n_0, fac n_0 también lo será (recordemos que a = b mod n => ac = bc mod n, en particular para a = fac n_0 , b = 0, c = n_0 +1, etc). Para concluir, no hace falta que n_0 sea 2^n si no que la suma de las multiplicidades del 2 como factor de los enteros menores o iguales a n_0 sea mayor o igual a n. Ejemplo: Si n fuera 3, no haría falta calcular fac 8 si no que fac 6 bastaría puesto que fac 6 = 6*5*4*3*2 = (2*3)*5*(2*2)*3*2 y en este caso la multiplicidad del 2 como factor es 4 >= 3. 

Todo esto también explica por qué cuando quitábamos dígitos del parámetro al principio seguía dando 0 y al quitar suficientes daba algo negativo. Al principio la multiplicidad del 2 seguía siendo suficientemente grande. En el momento que pasó a ser menor a n, empezamos a ver el resultado de la operación (que no era múltiplo de 2^n) reducido mod 2^n.

En respuesta a Gabriel Emmanuel Mello Muñoz

Re: fac 5483217492187432 = 0

de Juan Pablo García Garland -
Tenés razón!!!

Antes que nada, para quien lee sin contexto aclaro: esto es un problema que viene de representar enteros en un largo fijo (64 bits, por ejemplo). No es propio de Haskell ni de la programación funcional. Nosotros en clase implementamos `fac' de tipo `Int -> Int'. El tipo `Int' usa enteros de máquina. GHC nos provee del tipo `Integer' que tiene precisión arbitraria. Si usamos `Integer' no vamos a tener este `glitch'. La moraleja es que si van a trabajar con valores grandes, `Integer' es la forma adecuada.

Ahora sí, volviendo al problema: otra forma de decir lo mismo es que en general en la representación binaria, al multiplicar dos números, la cantidad de ceros a la derecha del resultado es la suma de la cantidad de ceros a la derecha de los factores. En el caso de `fac' además el resultado crece rápidamente, entonces muy pronto tenemos overflow (por eso los números "negativos"), pero después a esos resultados incorrectos los seguimos multiplicando por números pares, entonces internamente seguimos acumulando ceros, hasta que eventualmente llegamos a cero, para siempre :)

En mi sistema (x86_64), `fac 65' es el último valor no nulo. Da -9223372036854775808. Ejercicio de otro curso: Calcular la representación de ese número en complemento a 2. El resultado sería una sorpresa si no fuera porque ahora entendemos lo que pasó :)