Tipos Numéricos en Golang

GolangBeginner
Practicar Ahora

Introducción

Bienvenidos Gophers a este nuevo capítulo. En esta sección, aprenderemos sobre los tipos numéricos. El contenido incluye los tipos enteros de uso común, los tipos de punto flotante (floating-point types), los tipos booleanos, así como los números complejos y la sintaxis de valores literales introducida en la versión 1.13.

Puntos Clave (Knowledge Points):

  • Tipos enteros (Integer types)
  • Tipos de punto flotante (Floating-point types)
  • Tipos booleanos (Boolean types)
  • Números complejos (Complex numbers)
  • Sintaxis de valores literales (Literal value syntax)

Enteros (Integers)

Los enteros (integers) se pueden dividir ampliamente en dos categorías: enteros sin signo (unsigned integers) y enteros con signo (signed integers). Los enteros con signo son los más utilizados.

Sin signo (Unsigned) significa que solo puede representar números no negativos (0 y números positivos), mientras que los números con signo (signed) pueden representar tanto números negativos como no negativos.

Los enteros sin signo (unsigned integers) se pueden dividir en cuatro tamaños: 8 bits, 16 bits, 32 bits y 64 bits, representados por uint8, uint16, uint32 y uint64, respectivamente. Los enteros con signo (signed integers) correspondientes son int8, int16, int32 e int64. La siguiente tabla muestra los diferentes rangos representados por cada tipo:

Tipo Descripción Rango
uint8 Entero sin signo de 8 bits (8-bit unsigned int) 0 a 255
int8 Entero con signo de 8 bits (8-bit signed int) -128 a 127
uint16 Entero sin signo de 16 bits (16-bit unsigned int) 0 a 65535
int16 Entero con signo de 16 bits (16-bit signed int) -32768 a 32767
uint32 Entero sin signo de 32 bits (32-bit unsigned int) 0 a 4294967295
int32 Entero con signo de 32 bits (32-bit signed int) -2147483648 a 2147483647
uint64 Entero sin signo de 64 bits (64-bit unsigned int) 0 a 18446744073709551615
int64 Entero con signo de 64 bits (64-bit signed int) -9223372036854775808 a 9223372036854775807

Tomemos uint8 e int8 como ejemplos. Ambos son enteros de 8 bits y pueden representar 256 valores. En el tipo entero sin signo (unsigned integer) uint8, el rango que puede representar es de 0 a 255, mientras que en el tipo entero con signo (signed integer) int8, el rango que puede representar es de -128 a 127.

Además de los 8 tipos anteriores, existen otros tres tipos enteros especiales, uint, int y uintptr, donde uint e int pueden representar diferentes rangos en diferentes plataformas, y uintptr se utiliza para almacenar direcciones de puntero.

Tipo Rango
uint uint32 en sistemas de 32 bits, uint64 en sistemas de 64 bits
int int32 en sistemas de 32 bits, int64 en sistemas de 64 bits
uintptr Tipo entero sin signo (Unsigned integer type) utilizado para almacenar direcciones de puntero, utilizado principalmente en programación de bajo nivel como operaciones inseguras (unsafe operations)

Ahora, creemos un archivo llamado integer.go para demostrar el uso de enteros:

cd ~/project
touch integer.go
```go
package main

import (
    "fmt"
    "unsafe"
)

func main() {
    // View the type of int in the current environment
    // Declare a as the type int
    var a int
    // Use unsafe.Sizeof() to output the memory size occupied by the type
    fmt.Printf("The type int in the current environment is %d bits\n", unsafe.Sizeof(a)*8)

    var b int8 = 125
    // Use the %d placeholder in fmt.Printf to output the value of the integer
    // Use the %T placeholder in fmt.Printf to output the type of the variable
    fmt.Printf("The value of b is %d, and the type is %T\n", b, b)

    // Integer operations
    // Declare integers c and d, and calculate their sum
    c, d := 2, 3
    fmt.Printf("c + d = %d\n", c+d)
    // 10 - 5
    fmt.Printf("10 - 5 = %d\n", 10-5)
    // 8 * 10
    fmt.Printf("8 * 10 = %d\n", 8*10)

    // Example of uintptr usage - storing an address
    var ptr uintptr
    x := 42
    // Convert the pointer to x to uintptr
    ptr = uintptr(unsafe.Pointer(&x))
    fmt.Printf("The address of x stored in ptr is: %v\n", ptr)
}
```

Después de ejecutar el programa, obtenemos la siguiente salida:

```bash
go run integer.go
```

```plaintext
The type int in the current environment is 64 bits
The value of b is 125, and the type is int8
c + d = 5
10 - 5 = 5
8 * 10 = 80
The address of x stored in ptr is: 824634818784
```

**Explicación de la salida:**

- `The type int in the current environment is 64 bits`: Esta línea muestra que el tipo `int` en el sistema donde se ejecuta el código es de 64 bits (u 8 bytes), lo que indica que es un `int64`. `unsafe.Sizeof(a)` devuelve el tamaño de la variable `a` en bytes, y multiplicar por 8 lo convierte a bits. Esto significa que el tipo `int` puede contener valores enteros más grandes.
- `The value of b is 125, and the type is int8`: Aquí, hemos declarado una variable `b` de tipo `int8` y le hemos asignado el valor 125. La salida confirma esto mostrando tanto el valor como el tipo de datos.
- `c + d = 5`, `10 - 5 = 5`, `8 * 10 = 80`: Estas líneas muestran operaciones aritméticas enteras básicas: suma, resta y multiplicación. La salida confirma los resultados correctos de estos cálculos.
- `The address of x stored in ptr is: 824634818784`: Esto demuestra cómo se puede usar `uintptr` para almacenar una dirección de memoria. Estamos convirtiendo un puntero a la variable entera `x` a un tipo `uintptr`. El valor real de la dirección variará cada vez que se ejecute el programa. Este es un caso de uso avanzado que se encuentra típicamente en operaciones inseguras (unsafe operations) y programación de sistemas.

En este archivo, la función `unsafe.Sizeof()` se puede utilizar para obtener el número de bytes ocupados por el tipo de variable actual. 1 byte (byte) es igual a 8 bits, por lo que `unsafe.Sizeof()*8` puede obtener el número de bits ocupados por el tipo. De la salida, podemos ver que el entorno en línea es de 64 bits. El tipo real de `int` en el entorno en línea es `int64`.

Podemos usar el siguiente comando en la terminal para determinar la arquitectura del sistema actual:

```bash
dpkg --print-architecture
```

```plaintext
amd64
```

Números de Punto Flotante (Floating-Point Numbers)

Los números de punto flotante (floating-point numbers) representan números reales con partes fraccionarias en Go utilizando dos tipos: float32 y float64. El tipo de punto flotante predeterminado es float64.

float32 y float64 representan diferentes precisiones. La precisión predeterminada de float64 es mayor que la de float32.

El estándar IEEE 754 es el estándar de cálculo de punto flotante más utilizado en las computadoras, y las CPU modernas son compatibles con este estándar. Al igual que otros lenguajes de programación, Go también utiliza IEEE 754 para almacenar números de punto flotante.

Sabemos que un byte en una computadora puede almacenar 8 bits. float32 es un número de punto flotante de precisión simple (single-precision floating-point number) y ocupa 4 bytes, que son 32 bits. float64 es de doble precisión (double-precision) y ocupa 8 bytes, que son 64 bits.

En float32, el bit de signo ocupa 1 bit, el exponente ocupa 8 bits y los 23 bits restantes se utilizan para representar la mantisa.

En float64, el bit de signo también ocupa 1 bit, el exponente ocupa 11 bits y los 52 bits restantes se utilizan para representar la mantisa.

El valor máximo que float32 puede representar es aproximadamente 3.4e+38 en notación científica, y el valor mínimo es 1.4e-45. El valor máximo que float64 puede representar es aproximadamente 1.8e+308, y el valor mínimo es 4.9e-324. Podemos ver que el rango de valores de punto flotante puede ser desde muy pequeño hasta muy grande.

Podemos usar la constante math.MaxFloat32 para representar el valor máximo en float32. Use la constante math.MaxFloat64 para representar el valor máximo en float64.

Representación de Números de Punto Flotante

Al imprimir números de punto flotante, podemos usar el marcador de posición (placeholder) %f de la función Printf del paquete fmt. Aquí hay un ejemplo:

cd ~/project
touch float.go
```go
package main

import (
    "fmt"
    "math"
)

func main() {
    // Output without exponential form
    fmt.Printf("2.333 without exponential form: %f\n", 2.333)
    fmt.Printf("Pi without exponential form: %f\n", math.Pi)
    // Use %.2f to keep two decimal places for Pi
    fmt.Printf("Pi with two decimal places: %.2f\n", math.Pi)
    fmt.Printf("The maximum value of float32: %f\n", math.MaxFloat32)
    // Exponential form
    fmt.Printf("2.333 in exponential form: %e", 2.333)
}
```

Ejecute el comando y verá la siguiente salida.

```bash
go run float.go
```

```plaintext
2.333 without exponential form: 2.333000
Pi without exponential form: 3.141593
Pi with two decimal places: 3.14
The maximum value of float32: 340282346638528859811704183484516925440.000000
2.333 in exponential form: 2.333000e+00
```

**Explicación de la salida:**

- `2.333 without exponential form: 2.333000`: Esto muestra el número `2.333` impreso usando el marcador de posición `%f`. De forma predeterminada, el marcador de posición `%f` muestra 6 dígitos después del punto decimal, por lo que `2.333` se convierte en `2.333000`.
- `Pi without exponential form: 3.141593`: Esta línea imprime el valor de la constante `math.Pi`, que es una aproximación de la constante matemática π. El marcador de posición `%f` lo muestra con total precisión.
- `Pi with two decimal places: 3.14`: Al usar `%.2f`, le decimos a `fmt.Printf` que formatee el número a dos dígitos después del punto decimal, lo que resulta en `3.14`. Esto es muy útil cuando solo necesita una cierta precisión para la salida.
- `The maximum value of float32: 340282346638528859811704183484516925440.000000`: Esto muestra el valor máximo que un `float32` puede representar. Tenga en cuenta que es un número muy grande, y cuando se imprime con `%f`, se representará con muchos dígitos.
- `2.333 in exponential form: 2.333000e+00`: Esta línea muestra cómo representar un número de punto flotante en notación exponencial (científica) usando el marcador de posición `%e`. El `e+00` al final indica que multiplicamos `2.333000` por 10 elevado a la potencia de 0, que es simplemente `2.333`. Si el exponente fuera `e+02`, entonces el número se multiplicaría por `10^2` (100), lo que resultaría en `233.3`.

Tipos Booleanos (Boolean Types)

El tipo bool solo tiene dos valores posibles: true o false, siendo false el valor predeterminado. Tiene las siguientes características:

  • No se puede convertir a otros tipos, como convertir un entero a un booleano o convertir un booleano a un entero.
  • No puede participar en operaciones aritméticas.

Los tipos booleanos se utilizan generalmente en conjunto con operadores relacionales, como =, >, y <. Creemos un archivo llamado bool.go para ver una demostración:

cd ~/project
touch bool.go
```go
package main

import (
    "fmt"
)

func main() {
    // Use the %t placeholder in fmt.Printf to represent a boolean value
    fmt.Printf("Is 3 equal to 2? %t\n", 3 == 2)
    fmt.Printf("Is 2 equal to 2? %t\n", 2 == 2)

    // Determine whether a and b are equal
    a, b := 1, 2
    fmt.Printf("a is %d, b is %d\n", a, b)
    if a > b {
        fmt.Println("a is greater than b")
    } else if a == b {
        fmt.Println("a is equal to b")
    } else {
        fmt.Println("b is greater than a")
    }
}
```

Después de ejecutar el programa, obtenemos la siguiente salida:

```bash
go run bool.go
```

```plaintext
Is 3 equal to 2? false
Is 2 equal to 2? true
a is 1, b is 2
b is greater than a
```

**Explicación de la salida:**

- `Is 3 equal to 2? false`: La expresión `3 == 2` se evalúa como `false`, que luego se imprime usando el marcador de posición (placeholder) `%t`.
- `Is 2 equal to 2? true`: La expresión `2 == 2` se evalúa como `true`, que luego se imprime usando el marcador de posición `%t`.
- `a is 1, b is 2`: Esta línea imprime los valores asignados de las variables enteras `a` y `b`.
- `b is greater than a`: La declaración condicional `if-else if-else` se evalúa como `a < b`, que es `1 < 2` (verdadero), por lo tanto, se ejecuta la declaración `else`, lo que resulta en que el programa imprima "b is greater than a".

En este programa, primero usamos `%t` en `fmt.Printf` para representar un valor booleano, y luego usamos la declaración `if` para determinar la relación entre dos valores. En `fmt.Printf`, podemos usar el marcador de posición `%d` para representar un entero, el marcador de posición `%f` para representar un número de punto flotante y el marcador de posición `%t` para representar un valor booleano.

Números Complejos (Complex Numbers)

Go también tiene tipos de números complejos incorporados (built-in), que se pueden dividir en tipos complex64 y complex128. En complex64, tanto la parte real como la parte imaginaria son de 32 bits, mientras que en complex128, tanto la parte real como la parte imaginaria son de 64 bits. Podemos realizar fácilmente operaciones con números complejos en Go.

Cree un archivo llamado complex.go e ingrese el siguiente código:

cd ~/project
touch complex.go
```go
package main

import (
    "fmt"
)

func main() {
    // Initialize complex numbers in different ways
    c1 := complex(3, 1)
    c2 := 4 + 5i

    // Complex number operations
    c3 := c1 + c2
    c4 := c1 * c2
    // Use the real() function to obtain the real part of a complex number, and the imag() function to obtain the imaginary part of a complex number
    fmt.Printf("The real part of c1 is %v, the imaginary part is %v\n", real(c1), imag(c1))
    // %v in fmt.Printf can be used to represent complex numbers
    fmt.Printf("c1 + c2 is %v\n", c3)
    fmt.Printf("c1 * c2 is %v\n", c4)
}
```

Después de ejecutar el programa, obtenemos la siguiente salida:

```bash
go run complex.go
```

```plaintext
The real part of c1 is 3, the imaginary part is 1
c1 + c2 is (7+6i)
c1 * c2 is (7+19i)
```

**Explicación de la salida:**

- `The real part of c1 is 3, the imaginary part is 1`: La función `real(c1)` extrae la parte real del número complejo `c1`, que es `3`, e `imag(c1)` extrae la parte imaginaria de `c1`, que es `1`. Estos valores se imprimen luego usando el marcador de posición (placeholder) `%v`.
- `c1 + c2 is (7+6i)`: Esta línea muestra el resultado de sumar los números complejos `c1` y `c2`. `c1` se define como `3 + 1i` y `c2` como `4 + 5i`. Sumando las partes reales e imaginarias por separado da `(3 + 4) + (1 + 5)i`, o `7 + 6i`.
- `c1 * c2 is (7+19i)`: Esta línea muestra el resultado de multiplicar los números complejos `c1` y `c2`. `(3 + 1i) * (4 + 5i)` se calcula como `(3*4 - 1*5) + (3*5 + 1*4)i`, que se simplifica a `(12-5) + (15+4)i`, y finalmente a `7+19i`.

En este programa, demostramos cómo inicializar y realizar operaciones con números complejos utilizando la función `complex` y los operadores `+` y `*`. `real()` e `imag()` se utilizan para extraer las partes real e imaginaria de un número complejo respectivamente. El verbo `%v` en `fmt.Printf` se utiliza como un marcador de posición general para mostrar números complejos.

Sintaxis de Valores Literales (Literal Value Syntax)

En la versión 1.13, Go introdujo la Numeric Literal Syntax (Sintaxis de Literales Numéricos). Define la representación de números en diferentes sistemas numéricos. Echemos un vistazo a sus reglas.

  • Binario (Binary): Agregue 0b antes del entero. Por ejemplo, 0b101 es equivalente a 5 en decimal.
  • Octal: Agregue 0o o 0O antes del entero. Por ejemplo, 0o11 es equivalente a 9 en decimal.
  • Hexadecimal: Agregue 0x o 0X antes del entero. Por ejemplo, 0x1b es equivalente a 27 en decimal.
  • Use _ para separar dígitos en el entero, por ejemplo, 0b1000_0100_0010_0001 es equivalente a 0b1000010000100001. Esto puede mejorar la legibilidad.

Demostrémoslo en detalle:

cd ~/project
touch literals.go
```go
package main

import "fmt"

func main() {
    // Binary, add 0b at the beginning
    var a int = 0b101
    fmt.Printf("Binary a is %b, decimal is %d\n", a, a)

    // Octal, add 0o or 0O at the beginning
    var b int = 0o11
    fmt.Printf("Octal b is %o, decimal is %d\n", b, b)

    // Hexadecimal, add 0x or 0X at the beginning
    var c int = 0x1b
    fmt.Printf("Hexadecimal c is %x, decimal is %d\n", c, c)

    // Use separators
    d := 0b1000_0100_0010_0001
    e := 0b1000010000100001
    if d == e {
        fmt.Println("d is equal to e")
    }
}
```

Después de ejecutar el programa, obtenemos la siguiente salida:

```bash
go run literals.go
```

```plaintext
Binary a is 101, decimal is 5
Octal b is 11, decimal is 9
Hexadecimal c is 1b, decimal is 27
d is equal to e
```

**Explicación de la salida:**

- `Binary a is 101, decimal is 5`: Esta línea muestra la representación binaria de `0b101` que es `101`, y su equivalente decimal es `5`. El marcador de posición (placeholder) `%b` muestra el valor binario de `a`, mientras que `%d` muestra la representación decimal.
- `Octal b is 11, decimal is 9`: El número octal `0o11` es equivalente a `1*8^1 + 1*8^0 = 8 + 1 = 9` en decimal. El marcador de posición `%o` muestra la representación octal de `b`, y `%d` muestra su valor decimal.
- `Hexadecimal c is 1b, decimal is 27`: El número hexadecimal `0x1b` es equivalente a `1*16^1 + 11*16^0 = 16 + 11 = 27` en decimal. El marcador de posición `%x` muestra la representación hexadecimal de `c`, y `%d` muestra su valor decimal.
- `d is equal to e`: Esto muestra que el literal binario `0b1000_0100_0010_0001` es igual a `0b1000010000100001` en valor. Los guiones bajos son puramente para legibilidad y no cambian el valor del número. La condición `if` se evalúa correctamente como `true`.

En este programa, demostramos la declaración y salida de diferentes sistemas numéricos, así como el uso de separadores. En `fmt.Printf`, utilizamos diferentes marcadores de posición para representar diferentes sistemas numéricos. Por ejemplo, `%b` representa binario y `%x` representa hexadecimal. Dominarlos mejorará la eficiencia de la programación.

Resumen

Repasemos lo que hemos aprendido en esta sección:

  • Los enteros (integers) se pueden dividir en enteros con signo (signed integers) y enteros sin signo (unsigned integers).
  • Los tipos de enteros predeterminados (int) y sin signo (uint) tienen rangos que dependen de la plataforma.
  • Representación de números de punto flotante (floating-point numbers).
  • Cómo usar números complejos (complex numbers).
  • Introducción de la sintaxis de valores literales (literal value syntax).
  • Cómo usar diferentes marcadores de posición (placeholders).

En esta sección, explicamos y demostramos los tipos de enteros comunes, los tipos de punto flotante y los tipos booleanos. Discutimos sus tamaños, rangos y uso. También introdujimos números complejos y constantes literales. Los tipos numéricos son la piedra angular de los programas Go, y es importante que los estudiantes los estudien bien.