Mostrando entradas con la etiqueta logaritmos. Mostrar todas las entradas
Mostrando entradas con la etiqueta logaritmos. Mostrar todas las entradas

miércoles, 8 de enero de 2025

Python: dos maneras de calcular la cantidad de cifras de un número natural

Hace muchos años escribí una entrada titulada ¿Cuántas cifras tiene un número? Por ejemplo, el factorial de 1.000.000

Por aquel entonces seguía programando principalmente en lenguaje C++ y profundizaba en software matemático, como wxMaxima, Mathematica, Sage, etc.

Empezaba a hacer alguna cosita en Python y ahora el lenguaje que más utilizo, por diferentes motivos que ahora no vienen al caso.

 https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/Python.svg/240px-Python.svg.png

El tema es que resolviendo el problema 11 del Advent of Code 2024 hay que calcular la cantidad de cifras de números. Lo primero que pensé es utilizar la función len() sobre la conversión del número a cadena de caracteres:

len(str(natural))

Pero es cierto que, como comentaba en la entrada antigua, podemos calcular la cantidad de cifras de un número utilizando el logaritmo en base 10 (quedándonos la parte entera):

from math import log10

int(log10(natural))

Si sólo tenemos que calcular la cantidad de cifras de unos pocos números es prácticamente indiferente utilizar una manera u otra. Tal vez yo opte por la primera por no tener que importar funciones de librerías.

Sin embargo, si hay que realizar ese cálculo muchas muchas veces, ¿cuál será más rápido? Para contestar dicha pregunta he hecho dos programas sencillos para calcular el tiempo de ejecución.


    - Versión len

import time
inicio = time.time()
for i in range(1,100000000):
    len(str(i))
fin = time.time()
print(fin-inicio)

    - Version log10

from math import log10
import time
inicio = time.time()
for i in range(1,100000000):
    int(log10(i))
fin = time.time()
print(fin-inicio)


En mi ordenador el tiempo de ejecución del primer programa ronda los 11 segundos, mientras que el del segundo no llega a los 9 segundos.

Por tanto, en las condiciones planteadas, el logaritmo es la opción más rápida. Por otra parte, ¿qué pasa si el número puede ser 0? Dicho método no sirve. Si añadimos un condicional dentro del bucle, ¿seguirá siendo el método más rápido?

Lo dejo como ejercicio para quien quiera averiguarlo y compartir su respuesta :-)

viernes, 1 de marzo de 2013

El factorial de las mil y una noches.

Leyendo en Internet algunas cosas relacionadas con lo que se trata en la entrada anterior llegué a una de las "delicias digitales endiabladamente difíciles" (en palabras del autor). Se trata de una cuestión incluida en el capítulo 37 del libro La maravilla de los números, de Clifford A. Pickover. Así que desempolvo el libro, que lo tengo en mi estantería.


Incluyo aquí el fragmento al que me refiero:

"Desde la edad de trece años, cada 1001 días el doctor Googol lee Las mil y una noches, lo que significa que lee esa obra una vez cada 2,74 años. Con la excepción del Corán, ninguna otra obra literaria árabe se conoce mejor ni tiene más influencia en Occidente que Las mil y una noches. Esta colección de relatos está agrupada en derredor de uno central relativo a un sultán y a sus amantes. Después de descubrir que su esposa le había sido infiel, el sultán hace la promesa de tomar una nueva novia cada día y hacerla ejecutar al día siguiente.

Cuando Sahrazad fue elegida como su nueva esposa, cada atardecer ella le contaba un cuento al sultán, pero no lo terminaba, prometiendo hacerlo a la siguiente noche si sobrevivía. Esto continuó durante mil y una noches, hasta que el sultán quedó profundamente enamorado de Sahrazad y olvidó sus crueles planes de ejecución.

Una noche, después de haber leído Las mil y una noches, el doctor Googol empezó a preguntarse acerca de un número especial llamado el factorial de las mil y una noches. Este número está definido con el valor de x tal que x! tiene 1001 dígitos. Los factoriales crecen bastante rápidamente: 5!=120, 10!=3.682.800 y 15!=1.307.674.368.000. ¿Cuál es el factorial de las mil y una noches?"


Aprovechando lo que expliqué en la entrada anterior, lo que estamos buscando es el menor número x tal que x! tenga 1001 cifras, entonces, utilizando la expresión cifras de x! = [log(x!)] + 1 obtenemos:
1001 = [log(x!)]+1 que es equivalente a [log(x!)]=1000.

Como log(x!)=log(1)+log(2)+...+log(x) lo que podemos hacer es ir sumando los logaritmos decimales de los números naturales hasta que la parte entera del resultado sea 1000 (teniendo en cuenta que podríamos pasarnos de largo). O equivalentemente, hasta sumar el logaritmo decimal del número que haga que el resultado sea (por primera vez) mayor que 1000.

Con pequeñas modificaciones al programa de la entrada anterior construimos el siguiente código:


#include < stdlib.h >
#include < iostream >
#include < math.h >
main()
{
    double r=0;
    unsigned long int i=2, numero=1001;
    while(r < numero-1)
    {
        r+=log10(i);
        i++;
    }
    i--;  // quitamos el último incremento
    std::cout < < "El primer factorial cuyo resultado tiene por lo menos " < < numero < < " cifras es " < < i < < "!." < < std::endl;
    return 0;
}

La ejecución del programa anterior devuelve:
El primer factorial cuyo resultado tiene por lo menos 1001 cifras es 450!.

Podría ser que 450! tuviera más de 1001 cifras, dado que la condición que imponemos es que por lo menos tenga esa cantidad de cifras. Si ejecutamos el programa de la entrada anterior obtenemos:
El factorial de 450 tiene 1001 cifras.
O si alguien no se fía de mis dotes de programador, también puede preguntárselo a Wolfram Alpha :-)

Problema resuelto. Bueno, este en particular y el general de hallar el menor número x cuyo factorial tenga por lo menos n cifras (siempre que el tamaño de n no desborde la variable).


Curiosidad: Al igual que en la obra El prodigio de los números, Pickover dedica el libro al cuadrado mágico apocalíptico.

Clifford A. Pickover

¿Cuántas cifras tiene un número? Por ejemplo, el factorial de 1.000.000

El sistema de numeración que utilizamos es posicional y decimal, es decir que utilizando 10 dígitos (0, 1, 2, 3, 4, 5, 6, 7, 8 y 9) podemos escribir cualquier número entero.

¿Cuántas cifras tiene el número 92.368.556? Una pregunta bastante sencilla. Contamos y listo. 8 cifras.

¿Cuántas cifras tiene el factorial de 1.001? Pues ahora la cosa no es tan sencilla. Calcular el factorial de 1.001 y contar sus cifras es una posibilidad, pero en vista de que el número es bastante elevado vamos a intentar encontrar una alternativa que nos evite calcular el factorial.

Si volvemos al hecho de que usamos un sistema decimal, podemos concluir fácilmente que se añade una cifra más cada vez que damos un salto a la siguiente potencia de 10.
En 10¹ = 10 comienzan los números de 2 cifras.
En 10² = 100 comienzan los números de 3 cifras.
En 10³ = 1.000 comienzan los números de 4 cifras
... si generalizamos esta propiedad, podemos formularla como ...
En 10^n comienzan los números de n+1 cifras.

Si el cambio de cifras se produce en las potencias de 10, para conocer cuántas cifras tiene un número podemos utilizar la operación inversa a la exponenciación de base 10: los logaritmos decimales.


Por ejemplo, si tenemos en cuenta que log(10)=1 y log(100)=2, al calcular el logaritmo decimal de cualquier número entre 10 y 100 obtenemos, dado que el logaritmo es una función estrictamente creciente, un resultado decimal entre 1 y 2.

Entonces, la cantidad de cifras de un número cualquiera se obtiene sumando 1 a la parte entera de su logaritmo decimal.

Ejemplo: ¿Cuántas cifras tiene el número 92.368.556?
log(92368556) = 7,96552415434
La parte entera, a partir de ahora denotada por [], es [7,96552415434]=7.
Por tanto, el número 92.368.556 tiene 8 (7+1) cifras.

Si aplicamos esto mismo a la pregunta de cuántas cifras tiene 1001!, tendríamos:
cantidad de cifras de 1001! = [log(1001!)]+1

1001! es un número elevado para las calculadoras, pero wxMaxima puede calcular la expresión [log(1001!)]+1 directamente con el siguiente comando, sin que se produzca ningún desbordamiento:
entier(log(1001!)/log(10))+1;
Téngase en cuenta que en wxMaxima la función log corresponde al logaritmo neperiano y no al decimal, por eso aparece en la expresión la división entre log(10). El resultado que devuelve wxMaxima es:
2571
Bien, ya sabemos cuántas cifras tiene el factorial de 1001, pero además de que hemos hecho la gran trampa de que wxMaxima sí ha calculado el factorial de 1001, ¿qué pasa si el número es mayor? Pongamos por ejemplo que queremos saber cuántas cifras tiene 1.000.000!
Aquí wxMaxima ya no arroja resultados. Podríamos recurrir a la programación para definir una función que calcule el factorial multiplicando tipos enteros, pero la mayoría de lenguajes de programación tiene un rango limitado para sus tipos de variables. Por ejemplo, un tipo unsigned long long en el lenguaje de programación C tiene un rango de 0 a 18.446.744.073.709.551.615. Cualquier operación cuyo resultado sea mayor provoca un desbordamiento. Otros lenguajes de programación, como python, ya llevan implementadas en sus librerías funciones que efectúan las operaciones de este tipo sin provocar desbordamiento.
 
En cualquier caso, lo que estamos pensando es si puedo evitar el cálculo del factorial porque lo único que me interesa es saber cuántas cifras tiene. Así que volvemos a utilizar la expresión general:
cifras de n! = [log(n!)]+1

Nuestro problema era que no queremos/podemos calcular el factorial y si observamos la propiedad anterior resulta que ahora no sólo tengo que calcular un factorial sino que además tengo que calcular un logaritmo. Menudo invento. Bueno, tranquilidad. El factorial son productos y sabemos que los logaritmos tienen la propiedad de "transformar las multiplicaciones en sumas": log(a·b)=log(a)+log(b).
Así pues,
log(1000000!) = log(1000000·999999·...·2·1)=log(1000000)+log(999999)+...+log(2)+log(1).

¿Qué ganamos con esto? Pues reducir considerablemente el orden de los números con los que se trabaja. Los números decimales que aparecen en los sumandos de log(1000000)+...+log(2)+log(1) están comprendidos entre 0 y 6 (incluidos). La precisión de los tipos decimales de cualquier lenguaje de programación es más que suficiente para el cálculo anterior, especialmente si tenemos en cuenta que lo que nos interesa ahora es la parte entera del resultado del sumatorio.


Por ejemplo, en C++ podemos escribir el siguiente programa:



#include < stdlib.h >
#include < iostream >
#include < math.h >
main()
{
    double r=0;
    unsigned long int numero=1000000;
    for(unsigned long int n=1; n<=numero; n++)
    {
        r+=log10(n);
    } //for n
    unsigned long int cifras_factorial= (unsigned long int) r;
    cifras_factorial++;
    std::cout < < "El factorial de " < < numero < < " tiene " < < cifras_factorial < < " cifras." < < std::endl;
    return 0;
}


La ejecución del programa anterior devuelve la siguiente sentencia:
El factorial de 1000000 tiene 5565709 cifras.

Como curiosidad final, hice un programa en python que calcula todas las cifras del factorial de 1000000.


Entrada relacionada:
- El factorial de las mil y una noches.

martes, 29 de enero de 2013

Los contextos de la realidad: un ejemplo logarítmico

Las últimas semanas he estado ocupado con algunas cosas y por eso no he actualizado con ninguna entrada nueva el blog, aunque se me acumulan las ideas en la carpeta de borradores.

Algunas de las cosas que he estado preparando son las dos sesiones de las que me encargo en el curso de formación permanente de docentes de E. Primaria y E. Secundaria titulado "Conectar las matemáticas con la vida real", que se está realizando en el CEP de Inca.

Ayer se realizó la primera sesión de dicho curso, titulada "Los contextos de la realidad". Organicé la sesión dividida en dos bloques.

El objetivo de la primera parte era mostrar con un ejemplo cómo puede utilizarse un contexto de la realidad para a partir de este introducir un concepto matemático. Los asistentes realizaron una simulación de la tarea en la que empezamos hablando de terremotos y acabamos llegando al concepto de logaritmo.

La segunda parte de la sesión estaba dirigida a reflexionar sobre algunos aspectos relacionados con la utilización del contexto en clase de Matemáticas.

Adjunto un archivo con las presentaciones de diapositivas que utilicé como apoyo para el desarrollo de la sesión.

Espero que lo encontréis interesante.