Síguenos

Actualidad

Cómo calcular potencias en C sin recurrir a pow

Dos formas sencillas y una más rápida para elevar números en C, con sus límites y casos especiales explicados.

Publicado

el

potencias en c sin funcion pow

Elevar un número en C sin recurrir a la función de biblioteca consiste, en su forma más directa, en multiplicar la base por sí misma tantas veces como indica el exponente. Un bucle permite repetir esa operación; una función recursiva expresa la misma idea mediante llamadas a sí misma. Para exponentes grandes existe, además, un método más eficiente: la exponenciación por cuadrados.

La elección no depende solo de cómo escribir el código. También importa si el exponente puede ser negativo, si el resultado cabe en el tipo numérico elegido y cuánto tiempo lleva calcularlo. Los ejemplos de abajo trabajan con exponentes enteros no negativos y muestran cómo tratar el caso de exponente cero, que suele quedar escondido en las soluciones más breves.

La potencia como multiplicación repetida

En una expresión como 5 elevado a 3, el 5 es la base y el 3 es el exponente. Para un exponente positivo, el cálculo equivale a multiplicar la base por sí misma tres veces: 5 por 5 por 5 da 125. El programa no necesita una operación especial para cada potencia; basta con conservar un resultado parcial y actualizarlo en cada repetición.

El resultado parcial debe empezar en 1, no en la base. Ese valor es el elemento neutro de la multiplicación: multiplicar cualquier número por 1 lo deja igual. Además, hace que el caso de exponente cero salga de manera natural, porque el bucle no se ejecuta y la función devuelve 1. En aritmética y programación, esa convención representa la potencia de exponente cero para bases distintas de cero; el caso de cero elevado a cero tiene matices matemáticos que se explican más adelante.

Una función que calcula potencias enteras puede recibir la base y el exponente por separado. En los ejemplos se usa long long para la base y el resultado, y unsigned int para el exponente, de modo que este último no pueda ser negativo. Esto no garantiza que cualquier resultado quepa en el tipo: el rango de los tipos enteros depende de la implementación y debe tenerse en cuenta en el programa que los utilice.

Calcular el resultado con un bucle while

La versión iterativa mantiene una variable para el resultado y otra para las repeticiones pendientes. En cada vuelta multiplica el resultado acumulado por la base y reduce el exponente restante. Cuando ya no queda ninguna repetición, devuelve el acumulador. Esta formulación evita casos especiales dentro del bucle y funciona también si el exponente vale cero.

long long potencia_while(long long base, unsigned int exponente)
{
    long long resultado = 1;

    while (exponente > 0) {
        resultado *= base;
        exponente--;
    }

    return resultado;
}

Con una base de 9 y un exponente de 3, el acumulador pasa por 9, 81 y 729. El contador desciende en cada iteración hasta llegar a cero; entonces la condición deja de cumplirse. La operación esencial es resultado *= base, una forma abreviada de escribir que el resultado nuevo es el anterior multiplicado por la base.

El mismo cálculo puede escribirse con un bucle for, que reúne en una sola línea la inicialización del contador, la condición y su actualización. La lógica no cambia y tampoco la cantidad de multiplicaciones: si el exponente es n, el bucle realiza n productos. Para una primera implementación, while puede resultar más transparente porque el contador representa directamente cuántas multiplicaciones faltan; for puede ser más compacto cuando la repetición está acotada desde el inicio.

Hay una cuestión práctica que el ejemplo deliberadamente no resuelve: el desbordamiento. Si el resultado supera el valor máximo que puede representar long long, el comportamiento de la multiplicación con enteros con signo no está definido por el lenguaje C. El programa no está obligado a avisar ni a producir un valor que tenga sentido. Antes de usar una función así con entradas externas, hay que establecer qué rango se admite o implementar una comprobación de límites apropiada para el tipo y la operación.

La alternativa recursiva y su condición de salida

La recursividad consiste en que una función se invoque a sí misma con un problema más pequeño. Para una potencia, cada llamada reduce el exponente en una unidad y multiplica la base por el resultado de la llamada siguiente. La cadena termina cuando llega al exponente cero, que es la condición de salida necesaria para que no se produzcan llamadas indefinidamente.

long long potencia_recursiva(long long base, unsigned int exponente)
{
    if (exponente == 0) {
        return 1;
    }

    return base * potencia_recursiva(base, exponente - 1);
}

Al calcular 3 elevado a 4, la función pide primero 3 elevado a 3, después 3 elevado a 2 y continúa hasta llegar a 3 elevado a 0. Ese último caso devuelve 1; al deshacerse las llamadas pendientes, los resultados se multiplican por 3 hasta obtener 81. La recursión hace visible la definición matemática, aunque el ordenador debe guardar información de cada llamada mientras espera su resultado.

En esta versión, tanto el número de multiplicaciones como la profundidad de llamadas crecen proporcionalmente al exponente. Para valores modestos puede ser una forma clara de practicar la lógica recursiva, pero un exponente grande puede consumir demasiada pila de llamadas. El límite concreto depende del entorno de ejecución y no existe una cifra universal que pueda deducirse del código. El bucle, en cambio, no acumula una llamada por cada multiplicación y suele ser más adecuado para una repetición larga.

También importa no copiar sin revisar una condición como exponente menor o igual que cero si el parámetro es un entero con signo. En ese diseño, los exponentes negativos acabarían devolviendo 1, aunque en general esa no es la potencia matemática que se pretende calcular. Los ejemplos separan el problema usando un tipo sin signo para el exponente: la función deja explícito que admite únicamente exponentes enteros no negativos.

Una mejora para exponentes grandes: cuadrados sucesivos

El método repetitivo es sencillo, pero no es el único modo de organizar las multiplicaciones. La identidad de que una potencia par puede calcularse elevando al cuadrado una potencia menor permite reducir el trabajo. Por ejemplo, la base elevada a 8 se obtiene elevando al cuadrado la base elevada a 4, y esta, a su vez, se obtiene elevando al cuadrado la base elevada a 2. En lugar de multiplicar ocho veces, se aprovecha que cada paso reduce aproximadamente a la mitad el exponente.

Una versión iterativa mantiene la base actual, un acumulador y el exponente que aún falta procesar. Si el exponente es impar, incorpora la base actual al resultado; después divide el exponente entre dos y eleva la base al cuadrado para la siguiente etapa. El siguiente código usa un exponente sin signo y devuelve 1 si este vale cero.

long long potencia_rapida(long long base, unsigned int exponente)
{
    long long resultado = 1;

    while (exponente > 0) {
        if (exponente % 2 == 1) {
            resultado *= base;
        }

        exponente /= 2;

        if (exponente > 0) {
            base *= base;
        }
    }

    return resultado;
}

La condición de que el exponente sea impar se comprueba con el resto de dividirlo entre 2. Si queda resto 1, esa potencia de la base forma parte del resultado y se multiplica al acumulador. Dividir después el exponente entre 2 prepara el problema reducido; cuadrar la base conserva el valor matemático de la operación. La comprobación final evita hacer un cuadrado que ya no será necesario.

Este procedimiento necesita un número de etapas proporcional al logaritmo del exponente, no al exponente mismo. Para una potencia grande, la diferencia puede ser notable: el bucle sencillo hace una multiplicación por cada unidad del exponente, mientras que los cuadrados sucesivos reducen el número de pasos de forma drástica. Sin embargo, no eliminan el riesgo de desbordamiento. De hecho, los cuadrados intermedios también pueden superar el rango disponible aunque el resultado final que se esperaba presentar parezca pequeño o la aplicación no admita resultados tan grandes.

Qué cambia con exponentes negativos o bases decimales

Los ejemplos anteriores calculan potencias con exponente entero no negativo. Si el exponente es negativo, la regla matemática convierte la potencia en el inverso de la correspondiente potencia positiva: una base elevada a menos 3 equivale a 1 dividido por esa base elevada a 3. El resultado puede ser fraccionario, así que una función que devuelve un entero no puede representar en general el valor correcto. No basta con cambiar el tipo del exponente y dejar intacto el bucle.

Para incluir exponentes negativos hay que definir una operación con tipos de coma flotante, como double, y contemplar por separado la inversión del resultado. También hay que decidir qué hacer con una base cero y un exponente negativo, porque eso implica una división por cero. Los detalles dependen del contrato que se quiera dar a la función; una implementación para enteros no debe presentar un exponente negativo como si lo hubiera calculado correctamente.

Una base decimal, como 2.5, también exige un resultado de coma flotante si se quiere conservar la parte fraccionaria. En ese caso, multiplicar repetidamente puede calcular potencias con exponentes enteros no negativos, pero el resultado se representa con precisión finita y algunas fracciones no pueden almacenarse exactamente en binario. Si además se necesitan exponentes fraccionarios, el algoritmo de multiplicaciones repetidas deja de bastar: por ejemplo, calcular una raíz requiere otro método numérico o una función matemática de biblioteca.

La función estándar pow, declarada en el encabezado math.h, recibe argumentos de tipo double y devuelve un double. Es la opción habitual cuando se necesitan exponentes reales o negativos y no existe una exigencia de resolver el ejercicio sin funciones de biblioteca. En algunos entornos de compilación puede ser necesario enlazar la biblioteca matemática de forma explícita; la orden concreta depende del compilador y del sistema, por lo que no conviene asumir que una instrucción de compilación será idéntica en todas las plataformas.

El caso especial del exponente cero

La inicialización del acumulador a 1 hace que todas las implementaciones anteriores devuelvan 1 cuando el exponente es cero. Para una base distinta de cero, esa es la convención algebraica habitual y permite que muchas identidades de potencias sigan funcionando sin introducir excepciones. También explica por qué empezar el resultado en la base y ejecutar el bucle solo mientras el exponente sea mayor que uno es más delicado: el exponente cero puede quedar mal resuelto si no se trata aparte.

Cuando la base también es cero, la expresión cero elevado a cero no tiene una única interpretación en todas las ramas de las matemáticas. En programación, una función como las mostradas suele devolver 1 porque esa es la consecuencia directa del caso base; algunas bibliotecas o aplicaciones pueden adoptar otras reglas según el contexto. Por eso, el comportamiento para esa combinación debe formar parte de la definición de la función, sobre todo si los datos del programa pueden incluir ambos ceros.

Hacer explícita esa decisión evita que una elección de implementación se confunda con una verdad matemática universal. En una práctica de programación básica, devolver 1 suele ser lo esperado al usar un acumulador multiplicativo. En un programa científico o especializado, en cambio, el tratamiento puede venir determinado por el problema concreto y por las convenciones de la biblioteca utilizada.

Elegir el método según el problema

Para aprender cómo se construye una potencia, el while ofrece un recorrido fácil de seguir: multiplica, reduce el contador y termina. La recursión expresa la misma regla de forma compacta y sirve para entender las llamadas anidadas, aunque añade consumo de pila. Cuando el exponente puede ser grande y se busca reducir operaciones, los cuadrados sucesivos ofrecen una alternativa más eficiente, tanto en su versión iterativa como en una función recursiva de profundidad logarítmica.

La diferencia entre estos métodos no cambia las limitaciones numéricas. Un tipo entero solo representa un intervalo finito; uno de coma flotante admite valores fraccionarios, pero con precisión limitada. La función debe escoger tipos coherentes con la base, el exponente y el resultado esperado, y el código que la llama debe conocer qué entradas acepta. Una potencia calculada sin errores de sintaxis todavía puede ser matemáticamente inadecuada si el rango o el tipo no corresponden al problema.

En ejercicios introductorios, la multiplicación repetida cumple bien su propósito: convierte una definición sencilla en un bucle verificable. Fuera de ese contexto, conviene distinguir entre la necesidad de practicar una operación elemental y la de obtener un resultado fiable para cualquier exponente. C permite construir ambas soluciones, pero la elección correcta empieza por precisar qué valores se admiten y qué debe ocurrir cuando el resultado rebasa los límites representables.

Newsletter

Periodista internacional con más de 20 años de experiencia en radio, prensa y medios digitales. Licenciado en Ciencias de la Comunicación, trabaja entre España e Italia con la mirada puesta en la actualidad.

Lo más leído