Menu

Funciones matemáticas en C: math.h, sqrt, pow y el enlace con -lm

Un recorrido por math.h: sqrt, pow, fabs, floor y ceil, round, fmod, las familias trigonométrica y logarítmica, INFINITY y NAN, además de la bandera -lm del enlazador que provoca el clásico error de referencia indefinida.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

El núcleo del lenguaje C te da + - * / y %, y nada más: sin operador de potencia, sin raíz cuadrada, sin redondeo. Todo lo que va más allá de la aritmética vive en la biblioteca matemática estándar, declarada en <math.h>.

Todas estas funciones trabajan con precisión double: toman argumentos double y devuelven double. Eso es lo primero que hay que interiorizar, porque pasar un int funciona en silencio (se convierte) mientras que asignar el resultado a un int descarta la fracción en silencio.

Para empezar, y la bandera -lm

En tu propia máquina, compilar eso puede fallar:

/usr/bin/ld: /tmp/ccXYZ.o: in function `main':
program.c:(.text+0x1a): undefined reference to `sqrt'
collect2: error: ld returned 1 exit status

Este es con diferencia el tropiezo más común con math.h, y no es un error de tu código. La cabecera aportó las declaraciones, así que el compilador quedó satisfecho; las implementaciones viven en una biblioteca aparte (libm) que el enlazador no incluye por defecto en Linux. Agrega -lm:

gcc program.c -o program -lm

La bandera debe ir después de los archivos fuente: los enlazadores procesan sus entradas en orden, y una biblioteca listada antes que el código que la necesita no resuelve nada. En macOS y con MinGW en Windows, las rutinas matemáticas ya son parte de la biblioteca estándar de C, así que -lm es innecesaria (aunque inofensiva en macOS).

Potencias y raíces

sqrt(x) de un número negativo devuelve NaN en lugar de reventar; protege el argumento si puede ser negativo. hypot vale la pena conocerla porque calcula el mismo valor que sqrt(x*x + y*y) sin desbordarse cuando x o y son muy grandes.

Para potencias enteras pequeñas, la multiplicación simple es más rápida y exacta: x * x le gana a pow(x, 2.0), que pasa por logaritmos y puede devolver 8.999999999999998 donde esperabas 9.

Redondeo: floor, ceil, round, trunc

Cuatro funciones, cuatro respuestas distintas para los números negativos. Esa tabla es la parte que vale la pena memorizar:

  • floor(x): el mayor entero que no supera a x. Siempre se mueve hacia menos infinito: floor(-2.3) es -3.
  • ceil(x): el menor entero que no es menor que x. Siempre se mueve hacia más infinito: ceil(-2.3) es -2.
  • round(x): el entero más cercano, con los medios alejándose del cero: round(2.5) es 3, round(-2.5) es -3.
  • trunc(x): corta la parte fraccionaria, moviéndose hacia cero: trunc(-2.7) es -2.

Las cuatro devuelven un double. Convertir a int cuando quieres un entero está bien, pero fíjate en que (int)x por sí solo trunca, así que (int)(x + 0.5) es el viejo modismo de "redondear" y está mal para los números negativos. Usa round().

fabs y fmod

fabs es el valor absoluto para punto flotante. Recurrir a abs en su lugar es un error clásico, porque abs toma un int y la conversión ocurre en silencio:

% no funciona con double en absoluto: es un operador entero y el compilador lo rechaza. fmod(a, b) es su contraparte en punto flotante, y conserva el signo de a.

fabs también es la forma correcta de comparar dos doubles, ya que == sobre punto flotante es una trampa:

0.1 y 0.2 no se pueden representar exactamente en binario, así que su suma queda a un pelo de 0.3. Compara con una tolerancia, nunca con ==.

Trigonometría

Las funciones trigonométricas trabajan en radianes, no en grados: la otra fuente fiable de respuestas equivocadas.

El conjunto completo: sin, cos, tan, sus inversas asin, acos, atan, la atan2(y, x) de dos argumentos y la familia hiperbólica sinh, cosh, tanh. Prefiere atan2(y, x) sobre atan(y / x) para los ángulos: maneja x == 0 y acierta el cuadrante.

math.h define M_PI en la mayoría de los sistemas, pero es una extensión POSIX y no C estándar, así que puede faltar bajo -std=c17. Definir tu propia constante, como arriba, es portable.

Logaritmos y exponenciales

Fíjate en la trampa de nomenclatura heredada de las matemáticas: log() es el logaritmo natural (base e), no el de base 10. La base 10 es log10().

log(0.0) devuelve menos infinito y log(-1.0) devuelve NaN; ninguno de los dos revienta, lo que significa que un argumento malo viaja en silencio por todo tu cálculo hasta que algo imprime nan.

INFINITY, NAN y cómo comprobarlos

El punto flotante tiene valores que no son números, y math.h les pone nombre:

Esa última línea es la crucial. No puedes escribir if (x == NAN): siempre es falso. Usa isnan(x). (La identidad x != x solo es verdadera para NaN, que es como suele implementarse isnan y un recurso útil si alguna vez te topas con una cadena de herramientas que no la tenga.)

Los infinitos llegan por desbordamiento (exp(1000.0)) y por la división de un double distinto de cero entre cero, que —a diferencia de la división entera entre cero— está definida y no revienta.

Variantes float y long double

Cada función tiene tres formas: la versión double, una versión float con sufijo f y una versión long double con sufijo l.

float       sqrtf(float x);
double      sqrt(double x);
long double sqrtl(long double x);

Usa las versiones double a secas salvo que tengas una razón medida para no hacerlo: double es a lo que C promociona por defecto, y mezclar variantes float suele costar más en conversiones de lo que ahorra. El único lugar donde el sufijo importa es cuando un cálculo exclusivamente en float es crítico para el rendimiento en hardware sin soporte de doble precisión.

Un ejemplo práctico

Juntando varias de estas: la distancia entre dos puntos y las raíces de una ecuación cuadrática.

Fíjate en fabs(disc) < 1e-12 en lugar de disc == 0.0: la misma regla de la tolerancia de antes, aplicada donde un discriminante calculado a partir de valores medidos casi nunca caerá exactamente en cero.

<math.h> es una cabecera dentro de un conjunto mayor; la página de la biblioteca estándar mapea el resto, y números aleatorios cubre rand, que vive en <stdlib.h> y no aquí.

Preguntas frecuentes

¿Cómo uso funciones matemáticas en C?

Agrega #include <math.h> al inicio del archivo y llámalas: sqrt(16.0), pow(2.0, 10.0), fabs(-3.5). Toman y devuelven double. En Linux además debes enlazar la biblioteca matemática con -lm: gcc program.c -o program -lm.

¿Por qué obtengo 'undefined reference to sqrt' al compilar?

La cabecera le dio al compilador la declaración, pero la implementación vive en una biblioteca aparte que el enlazador no incorpora automáticamente en Linux. Agrega -lm al final del comando: gcc program.c -o program -lm. En macOS y con MinGW en Windows el código matemático forma parte de la biblioteca estándar de C, así que no hace falta ninguna bandera.

¿Cuál es la diferencia entre abs y fabs en C?

abs() de <stdlib.h> toma un int y devuelve un int. fabs() de <math.h> toma un double y devuelve un double. Llamar a abs(-3.7) convierte primero el argumento a int y da 3, descartando la fracción en silencio: usa fabs para valores de punto flotante.

¿Cómo compruebo si un valor es NaN en C?

Usa isnan(x) de <math.h>. No puedes comprobarlo con x == NAN, porque NaN resulta distinto de todo, incluido él mismo; esa rareza es de hecho la prueba de respaldo: x != x solo es verdadero para NaN. Usa isinf(x) para los infinitos e isfinite(x) para comprobar que es un número corriente.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR