1. Números enteros en Julia

Material desarrollado por Arturo Erdely para aprender programación en Julia. Más información en: Programación en Julia
BigInt
bitstring
Bool
convert
Int8
Int16
Int32
Int64
Int128
length
println
sizeof
typemax
typemin
typeof
UInt8
UInt16
UInt32
UInt64
UInt128
#
\t
_
Es posible realizar operaciones directamente en la línea de comandos:
4 - 99
Asignar valores a variables con nombres que distinguen minúsculas de mayúsculas. Aquello que se escriba después del símbolo #
se considera comentario y por tanto Julia no intenta ejecutarlo como código.
valorBestial = 666 # esto es un comentario
Mediante println
podemos mostrar el valor de una variable o el resultado de alguna operación:
println(valorBestial + 333)
También es posible utilizar caracteres especiales mediante comandos tipo . Por ejemplo, mediante \theta
seguido de la tecla de tabulador (TAB) se obtiene el símbolo mismo que puede utilizarse como nombre de objeto:
δ = 111
println(δ + 999)
Los números tipo Integer
(enteros) tienen los siguientes subtipos:
Signed
(con signo) que pueden ser a 8, 16, 32, 64 o 128 bits:Int8
,Int16
,Int32
,Int64
,Int128
Unsigned
(sin signo):UInt8
,UInt16
,UInt32
,UInt64
,UInt128
Bool
(booleano o lógico)BigInt
(entero de precisión arbitraria)
El tipo por defecto (default) será el de los bits que corresponda al equipo de cómputo que se esté utilizando, mismo que puede saberse mediante:
println(Sys.WORD_SIZE) # computadora actual ¿a cuántos bits?
1.1 Tipos Signed y Unsigned
Si asignamos un valor entero sin especificar el tipo de entero, será el tipo por default del equipo de cómputo que se esté utilizando, mismo que puede averiguarse mediante typeof
:
x = 5
println(typeof(x))
Si queremos especificar un tipo de entero distinto al tipo por defecto del equipo que se está utilizando, se hace mediante convert
:
y = convert(Int8, x)
println(typeof(y))
O simplemente:
z = Int8(5)
println(z)
println(typeof(z))
Para comprobar la diferencia sizeof
proporciona el tamaño en bytes de un objeto:
sizeof(x), sizeof(y)
Ahora comparemos sus representaciones como cadenas de bits con la función bitstring
:
bitstring(x) # el resultado aparece entre comillas por tratarse de una cadena de caracteres
Recordando que 1 byte = 8 bits entonces 8 bytes = 64 bits. Lo comprobamos con length
:
length(bitstring(x))
bitstring(y)
length(bitstring(y))
Así, con enteros tipo Int8
su representación como cadena de 8 bits es de la forma donde cada y corresponde al entero Así, por ejemplo, el entero como tipo Int8
se representa mediante 00000101
esto es:
bitstring(Int8(5))
Mediante cadenas de 8 bits que solo pueden tomar los valores 0 y 1 solo es posible representar un total de valores distintos. Mediante las funciones typemin
y typemax
podemos averiguar el entero mínimo y máximo representable mediante el tipo Int8
:
typemin(Int8), typemax(Int8)
Sucesivamente, comenzando en -128 y terminando en 127 tenemos un total de 256 números enteros distintos. El máximo entero tipo Int8
se representa mediante 01111111
:
bitstring(Int8(127))
y el mínimo entero positivo tipo Int8
se representa mediante la representación binaria que sigue a 01111111
que es 10000000
:
bitstring(Int8(-128))
Nótese que el cero es simplemente 00000000
:
bitstring(Int8(0))
Esto es, las cadenas de 8 bits iniciando en 00000000
y hasta 01111111
corresponden a los enteros 0, 1, 2, ..., 127. La cadena de 8 bits que sigue a 01111111
es 10000000
y se asocia al entero mínimo -128 como podemos comprobar:
bitstring(Int8(-128))
Luego -127, -126, y así sucesivamente hasta llegar a -1:
bitstring(Int8(-127)), bitstring(Int8(-126)), bitstring(Int8(-1))
Esto es, con cadenas de 8 bits, los enteros tipo Int8
pueden representar un total de 256 números enteros distintos, de los cuales la mitad (128) son los enteros no negativos del 0 al 127, y luego la otra mitad (128) los enteros negativos de -128 a -1. Así que mediante representaciones binarias sucesivas de 8 bits se codifican, en el siguiente orden, los enteros: 0, 1, ..., 126, 127, -128, -127, -126, ..., -2, -1. ¿Por qué en este orden? Hay una razón para ello que se ilustra con un sencillo ejemplo:
cincoPositivo = bitstring(Int8(5))
cincoNegativo = bitstring(Int8(-5))
(cincoPositivo, cincoNegativo)
Si hacemos la suma directa de estas dos representaciones binarias:
00000101
+
11111011
-------------
100000000
obtenemos una representación binaria de 9 bits, pero como solo tenemos (en este caso) espacio para 8 bits, el adicional se ignora (y por tanto se trunca) y queda el resultado como 00000000 (cero) que es justamente lo que corresponde a 5 + (-5) = 0.
El procedimiento anterior se conoce como codificación de enteros con signo en complemento a 2 que de forma general consiste en lo siguiente: en palabras de 𝑛 bits se reserva la representación binaria a enteros no negativos para después representar a enteros negativos como el entero no negativo y así garantizar que , que al truncarle el último bit queda como cero. Para más detalle sobre esto se recomienda ampliamente el libro Numerical Computing with IEEE Floating Point Arithmetic de Michael L. Overton (SIAM, 2001), y que cuenta con traducción al idioma español como Cómputo Numérico con Aritmética de Punto Flotante IEEE (Sociedad Matemática Mexicana, 2002).
Si renunciamos al signo, mediante 8 bits podemos representar los enteros del 0 al 255 mediante enteros tipo UInt8
:
typemin(UInt8), typemax(UInt8) # muestra representación hexadecimal
println(typemin(UInt8)) # representación decimal
println(typemax(UInt8)) # representación decimal
Es importante tener presente lo que sucederá si realizamos operaciones con números enteros cuyo resultado exceda el rango de representación del tipo de entero en cuestión. Por ejemplo, si al entero 255 tipo UInt8
le sumamos 1, esto convertirá a la cadena de bits 11111111
en 00000000
, esto es y que nadie se desgarre las vestiduras:
a = convert(UInt8, 255)
b = UInt8(1)
c = a + b
println(c)
println(typeof(c))
Volviendo al ejemplo donde al entero con signo tiene como representación en 8 bits a esto corresponde al entero sin signo y por lo tanto como entero sin signo de 8 bits tiene la misma representación binaria que el entero con signo en 8 bits:
bitstring(UInt8(251)), bitstring(Int8(-5))
De hecho, lo anterior podía verificarse por medio de la función reinterpret
:
println(reinterpret(UInt8, Int8(-5)))
println(reinterpret(Int8, UInt8(251)))
Así que en operaciones con enteros, de cualquier tipo, es importante estar consciente de los valores mínimo y máximo que puede manejar, para que no haya sorpresas con los resultados:
println("Int8 \t", 8*sizeof(Int8), " bits \t min = ", typemin(Int8), "\t max = ", typemax(Int8))
println("UInt8 \t", 8*sizeof(UInt8), " bits \t min = ", typemin(UInt8), "\t max = ", typemax(UInt8))
println("Int16 \t", 8*sizeof(Int16), " bits\t min = ", typemin(Int16), "\t max = ", typemax(Int16))
println("UInt16 \t", 8*sizeof(UInt16), " bits\t min = ", typemin(UInt16), "\t max = ", typemax(UInt16))
println("Int32 \t", 8*sizeof(Int32), " bits\t min = ", typemin(Int32), "\t max = ", typemax(Int32))
println("UInt32 \t", 8*sizeof(UInt32), " bits\t min = ", typemin(UInt32), "\t max = ", typemax(UInt32))
println("Int64 \t", 8*sizeof(Int64), " bits\t min = ", typemin(Int64), "\t max = ", typemax(Int64))
println("UInt64 \t", 8*sizeof(UInt64), " bits\t min = ", typemin(UInt64), "\t max = ", typemax(UInt64))
println("Int128 \t", 8*sizeof(Int128), " bits min = ", typemin(Int128), "\t max = ", typemax(Int128))
println("UInt128 ", 8*sizeof(UInt128), "bits\t min = ", typemin(UInt128), "\t max = ", typemax(UInt128))
Por ejemplo, lo siguiente genera un error por intentar convertir un entero fuera del rango admisible para el tipo que se pide:
Int8(128)
1.2 Tipo Bool
Aunque solo admite dos valores distintos false
y true
su representación es en 8 bits:
println("Bool \t", 8*sizeof(Bool), " bits \t min = ", typemin(Bool), "\t max = ", typemax(Bool))
d = false
e = true
println(typeof(d))
println(bitstring(d))
println(bitstring(e))
1.3 Tipo BigInt
Números enteros de precisión arbitraria. En este caso no existe una representación binaria de tamaño específico, de hecho la instrucción bitstring(BigInt(2))
arroja un error. Con BigInt
se puede tener tanta precisión en un número como lo permita el tamaño de la memoria de una computadora. Pero tiene un costo importante, además del consumo de memoria: menor velocidad en las operaciones. Si solo se realizarán unas pocas operaciones no se sentirá el impacto, pero ante un gran número de operaciones con números tipo BigInt
sí que comenzará a sentirse la diferencia respecto al uso de números con representación binaria fija a cierto número de bits. Esto ocurre porque las operaciones con representaciones binarias de número fijo de bits las realiza el hardware de la computadora, en contraste con las operaciones de números de precisión arbitraria que se realizan desde el software, y es eso último lo que provoca baja velocidad ante un número elevado de operaciones.
enteromaxU128 = typemax(UInt128) # máximo entero sin signo de 128 bits
println(enteromaxU128)
println(enteromaxU128 + 3) # por exceder el valor máximo para un UInt128
bb = BigInt(enteromaxU128)
bb3 = bb + 3
println(typeof(bb3), "\t", 8*sizeof(bb3), " bits")
println(bb3)
Así, por ejemplo, una forma incorrecta de calcular algo tan grande como sería:
3^500
En este caso utilizar tipo BigInt
:
BigInt(3)^500
Julia permite utilizar un separador de dígitos _
para una más cómoda lectura. Por ejemplo:
cifra = 100_000_000
Material desarrollado por Arturo Erdely para aprender programación en Julia. Más información en: Programación en Julia