Tipos Numéricos en Golang

GolangGolangBeginner
Practicar Ahora

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

## 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)

Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL go(("Golang")) -.-> go/BasicsGroup(["Basics"]) go(("Golang")) -.-> go/FunctionsandControlFlowGroup(["Functions and Control Flow"]) go(("Golang")) -.-> go/AdvancedTopicsGroup(["Advanced Topics"]) go/BasicsGroup -.-> go/values("Values") go/BasicsGroup -.-> go/variables("Variables") go/FunctionsandControlFlowGroup -.-> go/if_else("If Else") go/AdvancedTopicsGroup -.-> go/number_parsing("Number Parsing") go/AdvancedTopicsGroup -.-> go/base64_encoding("base64 Encoding") subgraph Lab Skills go/values -.-> lab-149067{{"Tipos Numéricos en Golang"}} go/variables -.-> lab-149067{{"Tipos Numéricos en Golang"}} go/if_else -.-> lab-149067{{"Tipos Numéricos en Golang"}} go/number_parsing -.-> lab-149067{{"Tipos Numéricos en Golang"}} go/base64_encoding -.-> lab-149067{{"Tipos Numéricos en Golang"}} end
## 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:

```bash
cd ~/project
touch integer.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:

go run integer.go
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:

dpkg --print-architecture
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:

```bash
cd ~/project
touch float.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.

go run float.go
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:

```bash
cd ~/project
touch bool.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:

go run bool.go
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:

```bash
cd ~/project
touch complex.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:

go run complex.go
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:

```bash
cd ~/project
touch literals.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:

go run literals.go
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 (Summary)

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.