Uso de tipos de punto flotante y complejos en Go

Los tipos de punto flotante son la forma en que Go representa números con decimales (números reales). A diferencia de los enteros, donde el valor es exacto, aquí trabajamos con una aproximación. Esto sucede porque las computadoras almacenan números en base 2 (binario), pero nosotros pensamos en base 10 (decimal). Esta discrepancia hace que muchos decimales comunes, como 0.1, no tengan una representación exacta en binario, similar a como el número 1/3 es un decimal infinito en nuestro sistema ($0.333…$).

En Go tienes dos tipos principales: float32 (precisión simple) y float64 (precisión doble). Casi siempre debes usar float64, ya que es el estándar de la librería math y ofrece un rango de precisión mucho mayor. Solo usa float32 cuando estés trabajando con arrays masivos de millones de elementos (como en modelos de Inteligencia Artificial o procesamiento de señales) donde el ahorro de memoria sea más importante que la precisión exacta.

Un detalle crítico es que Go sigue el estándar IEEE 754. Esto implica que existen valores especiales: NaN (Not a Number), que surge de operaciones inválidas como la raíz cuadrada de un número negativo, e Inf (Infinito), que resulta de dividir un número por cero. Por último, si necesitas trabajar en ingeniería o física, Go incluye tipos de números complejos (complex64 y complex128), que combinan una parte real y una imaginaria.

Si estás construyendo un sistema financiero, ten cuidado: Go no incluye un tipo Decimal en su librería estándar. Esto es porque los tipos de punto flotante son rápidos porque el hardware los procesa directamente, pero son inherentemente imprecisos para la contabilidad. Para manejar dinero, lo correcto es usar enteros (centavos) o librerías especializadas como github.com/shopspring/decimal.

package main

import (
	"fmt"
	"math"
)

func main() {
	// 1. Diferencia de precisión entre float32 y float64
	var f32 float32 = 0.1234567890123456789
	var f64 float64 = 0.1234567890123456789

	fmt.Printf("Float32: %.20f\n", f32)
	fmt.Printf("Float64: %.20f\n", f64)
	fmt.Println("--------------------------------------------------")

	// 2. El problema de la precisión: 0.1 + 0.2 no es 0.3
	sum := 0.1 + 0.2
	fmt.Printf("Suma de 0.1 + 0.2: %v\n", sum)
	fmt.Printf("¿Es 0.1 + 0.2 == 0.3?: %v\n", sum == 0.3)
	fmt.Println("--------------------------------------------------")

	// 3. Valores especiales: NaN e Inf
	nan := math.Sqrt(-1.0)
	inf := 1.0 / 0.0

	fmt.Printf("Raíz cuadrada de -1: %v (IsNaN: %v)\n", nan, math.IsNaN(nan))
	fmt.Printf("1.0 / 0.0: %v (IsInf: %v)\n", inf, math.IsInf(inf, 0))
	fmt.Println("--------------------------------------------------")

	// 4. Números complejos (parte real e imaginaria)
	// complex128 es el tipo por defecto para funciones como complex()
	c := complex(5, 2) // 5 + 2i
	fmt.Printf("Número complejo: %v\n", c)
	fmt.Printf("Parte real: %v, Parte imaginaria: %v\n", real(c), imag(c))
}

En el código anterior, fíjate cómo f32 pierde gran parte de la información decimal casi de inmediato comparado con f64; esto es porque tiene menos bits para almacenar la mantisa.

Al realizar la operación sum := 0.1 + 0.2, notarás que la comparación sum == 0.3 devuelve false. Esto es porque, internamente, la suma da algo como 0.30000000000000004. Es un comportamiento esperado del estándar IEEE 754, no un error de Go.

Cuando usamos math.Sqrt(-1.0), el resultado es NaN. Es vital usar math.IsNaN() para verificar esto, porque NaN tiene la propiedad extraña de que NaN == NaN es siempre false. Para el caso de inf, la función math.IsInf(inf, 0) nos permite saber si un número es positivo o negativo infinito.

Finalmente, al usar complex(5, 2), Go crea un tipo complex128. Para extraer sus componentes, utilizamos las funciones integradas real() e imag().

El error frecuente

Un error clásico es intentar comparar dos números de punto flotante usando el operador de igualdad == para verificar si son “lo mismo”.

// ESTO ES UN ERROR EN PRODUCCIÓN
var a float64 = 0.1 + 0.2
var b float64 = 0.3

if a == b {
    // Este bloque NUNCA se ejecutará
    fmt.Println("Son iguales")
}

Debido a los errores de redondeo acumulados, a y b casi nunca serán exactamente iguales. La forma correcta de comparar floats es verificar si la diferencia entre ellos es menor que un valor muy pequeño, llamado “épsilon” (epsilon).

// La forma correcta: comparar la diferencia absoluta
epsilon := 1e-9
if math.Abs(a - b) < epsilon {
    fmt.Println("Son iguales para fines prácticos")
}

19

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio