Tracks
/
Julia
Julia
/
Temario
/
Números
Nú

Números en Julia

7 ejercicios

Acerca de Números

Julia es un lenguaje de propósito general que se puede usar para la mayoría de las tareas de programación. En la práctica, sin embargo, sus principales casos de uso tienden a estar en la ingeniería y la ciencia. Los cálculos numéricos rápidos, versátiles y sofisticados son el eje de su diseño.

Números enteros

Un entero es un número «redondo», sin coma decimal.

En el concepto Basics vimos que se puede asignar un valor entero a una variable sin especificar un tipo.

Para mejorar la legibilidad se pueden usar guiones bajos como separador de dígitos. El compilador los ignora.

julia> x = 3
3

julia> typeof(x)
Int64

julia> large_number = 1_234_567_890
1234567890

Internamente, el compilador usará el tipo de entero con signo que resulte más apropiado para tu CPU. En los ordenadores modernos, normalmente será Int64, que es perfectamente adecuado para la mayoría de las tareas.

Los tipos se tratarán con más detalle en un concepto futuro. Por ahora, los tipos numéricos deberían ser lo bastante intuitivos como para que este documento tenga sentido.

Como veremos en un concepto posterior, Julia admite de forma nativa arrays muy grandes y multidimensionales. Estos pueden poner a prueba tanto la cantidad de memoria disponible como el ancho de banda para las transferencias de datos.

Para dar al programador un control más preciso, es posible y bastante habitual especificar el tipo de entero con signo o sin signo deseado. Muchas personas que aprenden Julia y que antes han usado Python quizá quieran comparar esto con NumPy, en lugar de con el Python básico.

julia> y = Int8(42)
42

julia> typeof(y)
Int8

julia> z = UInt32(1024)
0x00000400

Fíjate en que Julia, de forma predeterminada, muestra los enteros sin signo en formato hexadecimal, porque los tipos sin signo se usan a menudo para operaciones de bits de bajo nivel.

Los enteros también se pueden escribir en binario, octal o hexadecimal, con los prefijos 0b, 0o y 0x, respectivamente.

julia> a = 0x10
0x10

julia> Int(a)
16

julia> b = 0b1010 # displays as hexadecimal
0x0a

Desbordamiento de enteros y BigInt

Cada tipo de entero tiene un valor máximo y un valor mínimo que puede almacenar:

julia> typemax(Int8)
127

julia> typemin(Int8)
-128

Salirse de este rango válido provoca un desbordamiento de enteros, con resultados que parecen muy extraños.

julia> c = Int8(126)
126

julia> c * c
4

Seguramente estaremos de acuerdo en que 126 * 126 no debería ser igual a 4.

Una explicación detallada requiere saber cómo los registros de la CPU almacenan los enteros, algo que queda fuera del alcance de este concepto.

El problema se puede minimizar usando un tipo «más ancho», hasta Int128 o UInt128 (el significado de «más ancho» se explica más abajo). Sin embargo, todos tienen límites estrictos antes de toparse con problemas de desbordamiento.

Una solución es usar el tipo BigInt, que solo está limitado por la memoria de tu ordenador. Es la implementación que hace Julia de la aritmética de precisión arbitraria.

julia> 2 ^ 70
0

julia> big = BigInt(2) ^ 70
1180591620717411303424

julia> UInt128(big)
0x00000000000000400000000000000000

Como 2 elevado a 70 no cabe en un Int64, el primer cálculo del ejemplo anterior falla, aunque fíjate en que no hay ningún mensaje de error. ¡Cuidado, programador!

La conversión de tipo de 2 a un BigInt da la respuesta correcta, y mostrarlo en hexadecimal deja más claro que es una potencia de 2.

Números en coma flotante

No sorprenderá que los números en coma flotante tengan, de forma opcional, una coma decimal y una parte fraccionaria después de la coma.

julia> f = 3.45
3.45

julia> typeof(f)
Float64

Por supuesto, se admite la notación científica.

julia> avogadro = 6.02e23
6.02e23

Al igual que con los enteros, el tipo predeterminado es suficiente para la mayoría de los propósitos, pero hay otros tipos con signo disponibles. No existen tipos de coma flotante sin signo.

Como atajo, se pueden crear valores Float32 usando el sufijo f0.

julia> f32 = 4.56f0
4.56f0

julia> typeof(f32)
Float32

Otros tamaños de números en coma flotante necesitan una conversión de tipo explícita, igual que ocurre con los enteros.

Los valores máximo y mínimo pueden sorprenderte:

julia> typemax(Float64)
Inf

julia> typemin(Float64)
-Inf

¡El infinito es un valor válido!

Sin embargo, el rango útil de los números en coma flotante es limitado: cualquier valor muy grande recibe simplemente el valor Inf, y cualquier valor muy pequeño se redondea a 0.0.

Podemos usar otro par de funciones para ver estos límites: aproximadamente ± 10 ^ 308.

julia> floatmax(Float64)
1.7976931348623157e308

julia> floatmin(Float64)
2.2250738585072014e-308

Además, la precisión es limitada, ya que se representa un número grande pero finito de dígitos significativos (aproximadamente 15 para Float64).

Operadores aritméticos

Como se explicó en el concepto Basics, los operadores aritméticos funcionan casi igual que la aritmética estándar que se enseña a los niños. Fíjate en que la exponenciación usa ^, no ** (ambos son habituales en otros lenguajes).

2 + 3  # 5 (addition)
2 - 3  # -1 (subtraction)
2 * 3  # 6 (multiplication)
8 / 2  # 4.0 (division)
8 % 3  # 2 (remainder)
2 ^ 3  # 8 (exponentiation)

Sin embargo, merece la pena comentar algunos detalles específicos de Julia.

Multiplicación

julia> x = 4.2
4.2

julia> 2 * x
8.4

julia> 2x
8.4

julia> 2.4x
10.08

Eso puede sorprender.

Siempre es posible usar * como operador infijo, como en la mayoría de los demás lenguajes informáticos.

Sin embargo, Julia está diseñado por personas que creen que el código debería parecerse lo máximo posible a las ecuaciones matemáticas.

Como los nombres de las variables deben empezar por una letra, anteponer un número (entero o en coma flotante) al nombre se interpreta como una multiplicación implícita.

Por ejemplo, si queremos el área de la superficie de una esfera, en lugar de 4 * pi * r * r podríamos hacer esto:

julia> surface(r) = 4π * r^2
surface (generic function with 1 method)

julia> surface(3)
113.09733552923255

Aunque π es una constante incorporada, también es una letra (griega). Por eso, el analizador sintáctico sigue necesitando un * explícito para separar π de r.

División

Usar / como operador infijo siempre da un resultado en coma flotante, incluso cuando las entradas son números enteros.

Para la división entera hay más opciones:

julia> 10 / 3
3.3333333333333335

julia> div(10, 3)
3

julia> 10 ÷ 3
3

julia> 10 // 3
10//3

La función div() sirve para la división entera, y el resultado se trunca hacia cero: hacia abajo para los números positivos y hacia arriba para los negativos.

Como sinónimo, podemos usar el operador infijo ÷, de nuevo con la intención de que parezca más matemático. Si usas un editor que entiende Julia, escribe \div y luego pulsa la tecla <Tab>.

El operador // necesitará un concepto propio más adelante en el temario.

Por ahora, basta con decir que el resultado de // es un número «racional», el nombre formal de lo que la mayoría de la gente llama fracción. Se eliminarán los factores comunes del numerador y el denominador, para dar una razón de dos enteros en lo que se llama «mínima expresión».

julia> rationalnum = 22 // 6
11//3

julia> typeof(rationalnum)
Rational{Int64}

Ya tenemos los números racionales. ¿Y los números «irracionales»?

julia> π
π = 3.1415926535897...

julia> typeof(π)
Irrational{:π}

Un número irracional es aquel que no se puede reducir a una razón de enteros. Ejemplos habituales son π, e (el número de Euler) y muchas raíces, como √2 (la raíz cuadrada de 2).

Julia intenta hacer matemáticas como es debido.

Conversión entre tipos numéricos

Esto a menudo ocurre de forma automática:

julia> x = 2 + 3.5
5.5

julia> typeof(x)
Float64

Sumamos un Int64 a un Float64 y obtuvimos un resultado Float64.

De hecho, el entero se convirtió silenciosamente a Float64 antes de hacer la suma.

Julia tiene el concepto de «anchura» de los tipos numéricos.

  • Dentro de los enteros, y dentro de los números en coma flotante, no es más que el número de bits necesarios para el almacenamiento. Así, Int64 es más ancho que Int16.
  • Los números en coma flotante se consideran más anchos que los enteros, porque pueden almacenar cualquier parte fraccionaria.

Si se usa una mezcla de tipos dentro de una expresión, cada uno se «promociona» según sea necesario al tipo más ancho que se use.

Para forzar la conversión, podemos convertir un entero a un tipo específico, como en Float64(5).

Como alternativa, basta con usar float(5) y dejar que el compilador elija un tipo apropiado.

Las conversiones de coma flotante a entero son inevitablemente más complicadas. ¿Qué quieres hacer con lo que haya después de la coma decimal?

  • La función round() convierte al número entero más cercano, y en los empates, como 4,5, redondea al número entero par más cercano.
  • floor() redondea hacia abajo, ceil() hacia arriba y trunc() hacia cero.
  • Si intentas convertir directamente, por ejemplo con Int32(), fallará con un InexactError.

Sin embargo, de forma predeterminada, estas funciones no devuelven el tipo de entero que quizá querías. Se puede especificar el tipo de salida deseado.

julia> round(4.5)
4.0

julia> round(Int64, 4.5)
4

julia> round(Int, 4.5)  # => default integer type
4

julia> ceil(Int16, 4.3)
5

También es posible redondear a un número concreto de dígitos después de la coma decimal con la palabra clave digits.

julia> round(π, digits=10)
3.1415926536

Consulta el manual para más detalles.

División por cero

¿Seguro que esto simplemente lanza un error? En realidad, la situación no es tan sencilla.

La división entera con ÷ o // dará lugar a un error, como cabría esperar.

La división en coma flotante con / adopta lo que podría considerarse un enfoque de ingeniería, más que un enfoque estándar de la informática:

julia> 2 / 0
Inf

julia> 0 / 0
NaN

Como se explicó en una sección anterior, el infinito es un número en coma flotante válido en Julia, representado por Inf.

Cuando el numerador también es cero, el resultado está matemáticamente indefinido. Entonces Julia lo trata como «no es un número», representado por NaN.

Si esto te parece extraño, piénsalo en el contexto de trabajar con arrays grandes de datos del mundo real (y, por tanto, a menudo bastante desordenados). Para avanzar, lo mejor es simplemente marcar los valores problemáticos y seguir adelante.

Comprobar los valores manualmente sin fin sería tedioso de programar y, sin duda, perjudicaría el rendimiento en tiempo de ejecución.

¡Detenerse con un mensaje de error ante cada mínima anomalía haría que tu programa resultara muy impopular entre los usuarios!

Comparación de valores en coma flotante

Como se describe en el concepto Conditionals, las comprobaciones de igualdad se suelen hacer con el operador ==.

Esto funciona bien con enteros, caracteres, strings, etc. Sin embargo, los valores en coma flotante tienen una precisión limitada, y distintas formas de calcular el mismo resultado pueden dar valores ligeramente diferentes. Para Float64, esto suele estar en torno al decimoquinto dígito significativo: una diferencia pequeña, pero no «igualdad».

Tradicionalmente, el consejo para los programadores es no usar nunca == con valores en coma flotante: los resultados son impredecibles.

Una alternativa muy usada es comprobar el valor absoluto de la diferencia frente a cierta tolerancia permitida (a la que a menudo se llama épsilon o ϵ). Así, en lugar de a == b, usa abs(a - b) < epsilon.

Julia ofrece una alternativa más limpia con la función isapprox().

Para una tolerancia absoluta, la sintaxis es isapprox(a, b, atol=epsilon). En este caso, la palabra clave atol es obligatoria.

La tolerancia relativa suele ser más útil. Esta es la predeterminada, así que isapprox(a, b) intentará elegir un valor razonable para rtol (las reglas son bastante complicadas).

También se puede especificar una tolerancia relativa, como una fracción de los valores que se comparan. Así, isapprox(a, b, rtol=0.01 comprueba que los valores están dentro del 1 % el uno del otro.

Al estilo habitual de Julia, hay un operador matemático que es sinónimo del caso predeterminado: a ≈ b (el operador se escribe como \approx y luego <tab>).

Conceptos futuros relacionados

Además de los números racionales, otras partes posteriores del temario tratarán:

Editar en GitHub El enlace se abre en una nueva ventana o pestaña

Aprende Números

La práctica está bloqueada

Desbloquea 2 ejercicios más para practicar Números