7
C++

#include <math.h>
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
    
using namespace std;
    
int main()
{
    int cubo = 0;
    cubo = pow(5, 3);
    cout << "POW a traves de la variable 'cubo': " << cubo << endl; // me devuelve 124 (mal)
    cout << "POW directamente a consola: " << pow(5, 3); // me devuelve 125 (bien)    
    return 0;
}

Solo lo he detectado, de momento, con el 5, tanto al cuadrado como al cubo. En el primer caso, al cuadrado, me devuelve 24 por variable (mal), y 25 directo a consola (bien)

He seguido trasteando con este "problema", y he ideado un código para detectar qué numero elevado a qué potencia da un resultado diferente, si es pasado por variable o si sale directamente a consola.
Me explico después del código:

C++

#include <math.h>
#include <iostream>
#include <stdlib.h>
#include <stdio.h>

using namespace std;

int main()
{
    int resultado_variable = 0;
    int cantidad = 0;
    float potencia = 0;
    cout << "\nEscribe la cantidad de numeros a elevar a partir del 1: ";
    cin >> cantidad;
    cout << "Escribe la potencia a elevar: ";
    cin >> potencia;
    for (int i = 1; i <= cantidad; i++)
    {
        resultado_variable = pow(i, potencia);
        if (resultado_variable != pow(i, potencia))
        {
            cout << "\n" << i << "^" << potencia << endl;
            cout << "POW a traves de la variable: " << resultado_variable << endl;
            cout << "POW directamente a consola: " << pow(i, potencia) << endl;
        }
    }
    cout << "\n";
    return 0;
}

Tanto si la variable como el iterador del bucle FOR se declaran como float hasta donde he probado, el código no detecta diferencias, lo cual sería una solución satisfactoria para evitar este fenómeno, y efectuar un cálculo de cierta precisión.
Pero si la variable o el iterador se declaran como entero int, es entonces cuando aparecen resultados muy curiosos. Esto es, números iguales en pantalla que el código detecta como diferentes.
Efectivamente, no me cuadra lo del redondeo, ya que más bien parece que ante un resultado como 24,99999999..., que redondeando seguiría siendo 25, C++ no tiene en cuenta los decimales y se queda con el entero, en este caso 24.
Cosa lógica, creo yo, al declarar la variable como int.
Pero no termino de entender porqué eso mismo le afecta al iterador del bucle.

  • 1
    Es rato porque `pow` está definido en la cabecera `cmath` (o `math.h` en C), así que el código tal como lo tienes te debería dar error. – user3733164 Oct 14 '21 at 18:29
  • 1
    Llevas razón. #include lo tengo arriba del todo, y se quedó sin copiar. – Carlos.G.R. Oct 14 '21 at 18:34
  • Pues en mi caso no tengo problemas de comportamiento diferente. ¿Qué entorno de programación usas? Aprovecho para comentarte que es mejor incluir las cabeceras de C++ si estás en este lenguaje. Es decir: `cstdio`, y `cstdlib` (que por otra parte para este ejemplo tampoco valdrían para nada) y `cmath` – user3733164 Oct 14 '21 at 18:37
  • Visual Studio Code. El caso es que acabo de cambiar la variable "cubo" a float, en vez de int, y ya devuelve el resultado correcto, tanto al cuadrado como al cubo. Pero no termino de entender cual es la diferencia. Probaré tu consejo sobre cstdio, y cstdlib. Sobre cmath ya había leído algo, pero VSC no me la acepta. – Carlos.G.R. Oct 14 '21 at 18:39
  • Sí que es raro. A ver si alguien te puede echar una mano. Lo de cambiar a `float` no debería ser la solución, desde luego. Suerte. – user3733164 Oct 14 '21 at 18:42
  • Eso me ha parecido a mi... Mil gracias. Hasta otro rato. – Carlos.G.R. Oct 14 '21 at 18:45
  • 2
    Probá con `std::pow`, definido en ``, que es distinto de `pow` definido en ``. El de `` es `double pow (double base, double exponent)`, esto castea el resultado a `int` cuando lo llevas a la variable, pero posiblemente `std::cout` lo recibe como `double`, de ahí la diferencia por un error de representación de los enteros en `double` (que creía que no podía pasar para un entero tan chico). – D4RIO Oct 14 '21 at 19:23

4 Answers4

9

Las cabeceras <math.h>, <stdlib.h> y <stdio.h> son de y no deben usarse en .

Existe una versión adaptada a C++ de cada una de esas cabeceras, son respectivamente <cmath>, <cstdlib> y <cstdio>, debes usar esas y no las de C para compilar código C++.

No sólo eso, también debes evitar incluir cabeceras que no usas, tu corto código no usa nada de lo que proporciona <stdlib.h> ni <stdio.h>. Dicho esto, vamos a ver las diferencias entre <math.h> y <cmath>.


<math.h>

Dado que C no permite la sobrecarga de funciones, existen tres versiones de la función para tres dobletes de parámetros y una macro para simular sobrecarga de funciones:

  • double pow(double, double).
  • float powf(float, float).
  • long double powl(long double, long double).
  • #define pow(base, exponent).

Dado que en C no existen los espacios de nombres, todas las funciones están en el espacio de nombres global.

<cmath>

Igual que en la cabecera C, existen las mismas tres versiones de la función, junto a una versión con exponente entero y una versión plantilla que cubre todas las combinaciones de argumentos aritméticos no cubertas por las versiones no plantilla:

  • double pow(double, double).
  • float powf(float,float).
  • long double powl(long double, long double).
  • double pow(double, int).
  • float pow(float, int).
  • long double pow(long double, int).
  • tipo_promovido pow(tipo_aritmetico_1, tipo_aritmetico_2).

Todas estas funciones están en el espacio de nombres std.


Ahora que sabemos las diferencias entre ambas cabeceras, veamos que ha pasado:

  1. Se ha incluido la cabecera de C <math.h>.
  2. No existe versión de pow con parámetros enteros, así que se ejecuta la macro.
    1. Has llamado a la macro pow con parámetros enteros (int).
    2. Su funcionamiento es el siguiente: si cualquiera de los argumentos es de tipo int se llama pow convirtiendo los argumentos y el resultado a double.
  3. Guardas el resultado double en un entero, perdiendo la parte decimal.
  4. Imprimes el valor redondeado a la baja (pues perdió la parte decimal).

El error del redondeo no se da al imprimir directamente pues no guardas el resultado temporal en una variable entera, pero eso es dependiente de cómo el compilador y el sistema tengan configurados el tipo de redondeo.

PaperBirdMaster
  • 44,474
  • 6
  • 44
  • 82
  • Las cabeceras de compatibilidad del tipo `xxx.h` forman parte de la biblioteca estándar de C++ para interoperabilidad con C, de hecho para C++23 se agregarían a la sección de cabeceras del estándar propio de C++ (actualmente están en un anexo marcado como `deprecated`) principalmente porque hay código, sobre todo cabeceras, que debe ser a la vez C y C++ válido. Desaconsejar su uso en programas puros C++ está bien, pero decir que no son de C++ y no se deben usar no es correcto. [FUENTE](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2340r1.html). – D4RIO Oct 15 '21 at 01:29
  • Las cabeceras `` **NO forman parte del estándar C++**. Las cabeceras de compatibilidad son ``. Desaconsejo el uso de las primeras en todos los casos y de las segundas sólo cuando se incluyen sin necesidad. – PaperBirdMaster Oct 15 '21 at 08:01
  • 2
    Corrección, tienes razón @D4RIO forman parte del estándar C++ pero su estado es deprecado (como mencionas), por lo que actualmente el uso de cabeceras `` es desaconsejado en favor de las versiones adaptadas a C++ ``. – PaperBirdMaster Oct 15 '21 at 08:15
  • Exacto, y el estado en C++23 seguramente será "parte del estándar, desaconsejado para C++ puro, aconsejado para módulos políglotas que permiten su uso desde C o C++". Algo en los papers del comité que estandariza C++ me dice que vamos a ver más de estas cabeceras en las próximas 2 versiones de C++, junto con más compatibilidad con POSIX y C estándar. – D4RIO Oct 15 '21 at 19:08
  • Gracias a todos. Debe de tratarse de algo relacionado con el redondeo. Lo que más me ha llamado la atención es el uso de las librerías de C++ , y , pero en VSC me da error en cuanto las defino. Debe de faltarme alguna extensión o actualización que no consigo averiguar. – Carlos.G.R. Oct 15 '21 at 19:13
  • pero 5 al cuadrado/cubo no tiene parte decimal. ¿Cómo calcula `pow`? – Candid Moe Oct 17 '21 at 12:06
  • @CandidMoe porque tal y como explico, se está llamando a la versión de `pow` que usa parámetros `double`. – PaperBirdMaster Oct 17 '21 at 17:24
  • Si, eso lo vi. No me cuadra que 5.0 * 5.0 de algo distinto a 25.0 – Candid Moe Oct 17 '21 at 18:18
  • @CandidMoe Es lo que tiene trabajar con coma flotante. – PaperBirdMaster Oct 18 '21 at 07:26
  • @CandidMoe Interesante observación. Me ha parecido que merecía la pena añadir otra respuesta. – abulafia Oct 18 '21 at 09:41
6

Lo más normal es que sea un error por redondeos. No guardes un float en un int o perderás toda la parte decimal.

Por alguna razón el programa te estará devolviendo un resultado similar a 24.999999999999... al imprimir ese resultado el sistema lo redondea automáticamente a 25, pero si lo conviertes a entero, como los decimales se truncan, el resultado obtenido es 24

Para obtener más información sobre el tema puedes leer las respuestas de esta otra pregunta

¿Por qué mis programas no pueden hacer cálculos aritméticos correctamente?

eferion
  • 49,291
  • 5
  • 30
  • 72
4

Un error de redondeo en una operación tan simple como 5 elevado a 2 es chocante. Podrías pensar que pow() implementará esa operación multiplicando 5 por 5, en cuyo caso debería dar 25. Y aún si el 5 se está representando en coma flotante no hay forma de explicar errores de redondeo en la operación 5*5, ya que tanto 5.0 como 25.0 son representables de forma exacta en el estándar IEEE-754 (tanto el de precisión simple float, como el de precisión doble double).

Entonces ¿qué está pasando aquí?

Lo que ocurre es que pow(a, b) admite ambos parámetros de tipo float o double, para permitirte operaciones como elevar 5 a números fraccionarios. Por ejemplo 5 elevado a 0.5 sería la raíz cuadrada.

Para poder elevar a a b siendo b un número real arbitrario, la forma más sencilla de lograrlo es utilizar logaritmos. Es decir, se multiplica el logaritmo de a por b, y después se deshace el logaritmo. En definitiva: exp(log(a)*b).

Esto sí que puede explicar errores de redondeo. De hecho, mira el siguiente experimento que he realizado con python:

>>> 5.0 * 5.0 
25.0                         # No hay errores de redondeo aqui
>>> from math import log, exp
>>> exp(log(5.0)*2.0)
24.999999999999996            # Ajá!
>>> int(exp(2.0*log(5.0)))
24                            # Ajajáaaa!

Así que ya sabemos por qué te sale 24 si lo metes en una variable de tipo int. En cuanto a por qué sale 25.0 en vez de 24.999999999999996 cuando imprimes directamente el resultado de pow() se debe a que, a diferencia del REPL de python, C++ intenta dar una representación "amigable" de los números flotantes, redondeándolos a un numero razonable de decimales, en vez de mostrar hasta el último decimal. De hecho python también lo hace si usas el equivalente al "printf":

>>> print("%f" % exp(2.0*log(5.0)))
25.000000
abulafia
  • 53,696
  • 3
  • 45
  • 80
-2
       //HOLA!   
   ///soy nuevo y estoy aprendiendo a programar.
     ///Hice a mi parecer así:

 #include<iostream>
 using namespace std;

int Potencia(int ,int );
int Potencia(int x,int n){
int pot;
if(n==1){
    pot=x; 
 }else{
      pot=x*Potencia(x,n-1);
 }
return pot;
}
int main(){
int base;
int exponente;

cout<<"Digite la base:"<<" ";
    cin>>base;
cout<<"\nDigite el exponente:"<<" " ;
    cin>>exponente;
    cout<<"\nLa potencia es:"<<" "<<Potencia(base,exponente)<<endl; 
    return 0;
 }
  • 1
    Bienvenido/a a [es.so]: haz el [tour] para conocer el funcionamiento del sitio, y lee [answer]. ¿Qué hace este código? ¿Cómo esta respuesta responde a la pregunta? Puedes [edit] tu respuesta todas las veces que sea necesario. Ten en cuenta que la pregunta es de tipo _por qué sucede esto_, no del tipo _qué otro código podría utilizar_. – padaleiana Oct 16 '21 at 12:53
  • El OP está intrigado por la diferencia en los resultados y pide que alguien le explique. El OP claramente sabe calcular potencias, por lo que este método alternativo es innecesario. – Candid Moe Oct 17 '21 at 12:03