En el lenguaje C, una variable float ocupa 32 bits de memoria. Utiliza el estándar IEEE 754, que divide el número en un bit de signo, un exponente y una mantisa (fracción). El problema fatal es que los ordenadores piensan en potencias de 2 (base binaria), mientras que el dinero y el volumen se miden en potencias de 10 (base decimal). Ciertas fracciones decimales perfectas como 0.1 o 0.015 (la equivalencia volumétrica de tu sensor Hall) no tienen una representación binaria exacta. Son el equivalente binario de 1/3 (0.3333... infinito).
Cuando escribes float volumen = pulsos * 0.1;, el procesador realmente multiplica por 0.10000000149.... En un pulso, el error es invisible. Pero al acumular millones de pulsos durante meses en una estación de gas, ese error microscópico se acumula. La pantalla mostrará 10,000 Litros, pero la matemática subyacente se habrá desviado, provocando discrepancias de auditoría millonarias.
La industria aeroespacial y metrológica prohíbe el uso de floats para sumas acumulativas. En su lugar, utilizamos Tipos Enteros de Ancho Exacto (uint32_t, uint64_t). Elevamos la escala mentalmente: Si un pulso equivale a 15 mililitros (0.015 L), no usamos decimales. Guardamos el valor estrictamente como 15 en un entero sin signo. Toda la matemática se ejecuta en números enteros puros (imposibles de redondear mal). La conversión a decimal ($V = Entero \times 10^{-N}$) solo ocurre en el último milisegundo, exclusivamente como un truco visual para el display del usuario.
Paso 1: Eliminar el sprintf del Proyecto.
Las funciones de la librería estándar (stdio.h) que manejan cadenas de texto formateadas están diseñadas para sistemas operativos pesados. En un microcontrolador (como tu NXP Kinetis o STM32), llamar a sprintf(buffer, "%f", volumen); inyecta hasta 15 KB de código basura en tu memoria Flash. Debemos usar rutinas manuales de formato ASCII que aíslen la parte entera mediante división (/) y la parte fraccionaria mediante el operador módulo (%).
Paso 2: Acumuladores Seguros en 64 bits.
Al no usar floats, nuestras multiplicaciones producirán números masivos. Si tenemos 200,000 mililitros (200 L) y el precio es de \$1,045 centavos, multiplicar ambos ($200,000 \times 1045 = 209,000,000$) entra peligrosamente cerca del límite de un registro de 32 bits ($4.2 \times 10^9$). Todas las ecuaciones temporales de costo deben moldearse momentáneamente a uint64_t para eludir desbordamientos.
Este es el núcleo de facturación de tu Despachador. Nota la absoluta ausencia de variables con coma flotante, respetando el canon de programación industrial crítica que has venido forjando a lo largo del año.
/**
* @file fixed_point_metrology.c
* @brief Algoritmos de Precisión: Cálculo de Volumen y Costo en Punto Fijo
* Diseñado para inmunidad total contra errores IEEE 754 y latencia de FPU.
*/
#include <stdint.h>
// =========================================================================
// DEFINICIÓN DE ESCALAS Y CONSTANTES (Sin decimales)
// =========================================================================
// Escala volumétrica a 3 decimales (Resolución en Mililitros)
#define ESCALA_VOLUMEN 1000U
// Escala monetaria a 2 decimales (Resolución en Centavos)
#define ESCALA_MONEDA 100U
// Factor de Calibración Físico: 1 Pulso del sensor Hall = 15 Mililitros (0.015 L)
#define MILILITROS_POR_PULSO 15U
// Variables críticas almacenadas en SRAM
uint32_t pulsos_acumulados_totales = 0;
uint32_t precio_por_litro_centavos = 1045; // Equivale a $10.45
// =========================================================================
// MÁQUINA DE CÁLCULO ALGORÍTMICO
// =========================================================================
/**
* @brief Realiza el cálculo del volumen y costo actual con precisión perfecta.
* @param[out] vol_ml Puntero para retornar el volumen exacto en mililitros.
* @param[out] costo_c Puntero para retornar el costo exacto en centavos.
*/
void Calcular_Despacho_Exacto(uint32_t* vol_ml, uint32_t* costo_c) {
// 1. CÁLCULO DE VOLUMEN (100% Preciso)
// Una multiplicación entera de 1 ciclo de reloj.
*vol_ml = pulsos_acumulados_totales * MILILITROS_POR_PULSO;
// 2. CÁLCULO DE COSTO (Prevención de Overflow temporal)
// Para calcular el dinero, la ecuación teórica es: Costo = Volumen(L) * Precio(L)
// En punto fijo es: Costo_c = (Volumen_ml * Precio_c) / ESCALA_VOLUMEN
// Forzamos un casting explícito a 64 bits para el cálculo intermedio
// y evitamos un Integer Overflow silencioso (Día 359).
uint64_t calculo_temporal = (uint64_t)(*vol_ml) * (uint64_t)precio_por_litro_centavos;
// Dividimos para ajustar el factor cruzado de volumen y recuperar la escala de dinero
*costo_c = (uint32_t)(calculo_temporal / ESCALA_VOLUMEN);
}
// =========================================================================
// FORMATEADOR ASCII AGNÓSTICO (El reemplazo de sprintf)
// =========================================================================
/**
* @brief Inserta el punto decimal ilusorio para visualización humana.
*/
void Formatear_Pantalla_LCD(uint32_t valor_escalado, uint32_t divisor, char* string_salida) {
// Separación matemática
uint32_t parte_entera = valor_escalado / divisor;
uint32_t parte_fraccional = valor_escalado % divisor;
// En un sistema real, aquí llamaríamos a nuestras funciones ligeras (itoa).
// Fast_IntToStr(parte_entera, string_entero);
// Fast_IntToStr(parte_fraccional, string_fraccional); // Con padding de ceros a la izquierda
// Lógica esquemática de concatenación:
// strcpy(string_salida, string_entero);
// strcat(string_salida, ".");
// strcat(string_salida, string_fraccional);
}
Objetivo del día: Proporcionar la retórica técnica para destruir cualquier crítica a la complejidad algorítmica del firmware.
Es muy probable que, durante tu presentación, alguien observe tu código fuente y te cuestione: *"¿Por qué estás dividiendo el número para mostrarlo en pantalla en lugar de usar un simple `sprintf("%0.2f")`?"*. Esta es tu oportunidad de brillar como un Arquitecto de Silicio.
La respuesta estructurada es: *"Evito los números flotantes por tres razones críticas. Primero, la metrología del gas LP no tolera el error de representación infinita del estándar IEEE 754; un mililitro perdido en la memoria es dinero perdido. Segundo, las librerías `stdio.h` que habilitan `sprintf` inyectan Kilobytes de basura en mi memoria Flash, violando las reglas de austeridad del software embebido. Y tercero, para mantener la predictibilidad del tiempo de ejecución (Hard Real-Time) de mi procesador de 32 bits y asegurar que jamás pierda un pulso de los flujómetros, la Unidad Aritmético-Lógica (ALU) debe operar exclusivamente con enteros nativos de ciclo único. El punto decimal es una mera ilusión gráfica para la pantalla"*. Cuando digas esto, el jurado no tendrá más preguntas.
float obliga al compilador a inyectar rutinas de emulación por software que consumen cientos de ciclos de reloj, destruyendo el determinismo de cualquier RTOS o interrupción.uint32_t), multiplicando los valores reales por una constante de escala (ej. 1000 para mililitros) y garantizando cálculos en 1 ciclo de reloj con un 0% de error de redondeo.stdio.h y su función sprintf debe erradicarse del código de misión crítica. Introduce fragmentación, eleva drásticamente el consumo de memoria Flash e incrementa el riesgo de desbordamientos de búfer (Buffer Overflows) al formatear cadenas de texto dinámicas./ y módulo %) para reconstruir visualmente el punto decimal sin comprometer la base de datos interna.