La cantidad de cifras significativas que soporta C ++ se ven afectadas

Iniciado por snoopty, 24 Agosto 2021, 08:38 AM

0 Miembros y 1 Visitante están viendo este tema.

snoopty

Buenas noches a todos. Hice un programa, simplemente para comprobar la cantidad de cifras significativas que puedo mostrar para variables de tipo float, double y long double .... pero me encontré con dos sorpresas  - o a raíz de error mío, x supuesto - :
( por ejemplo para el tipo float )
1 - La teoría dice que C ++ acepta 7 Cifras significativas, pero muestro apróx. 14 y para ajustar y mostrar decimales después de los 14 rellena con ceros hasta completar el ancho pre - establecido
y ......
2 - definí una variable float p = 8.5361478025119782429925187 buscando a propósito que me restrinja  la variable a 7 decimales, cosa que no sucedió como dije antes PERO ADEMÁS muestra por pantalla correcyamente los primeros cuatro decimales, y luego rellena con números diferentes a la variable que definí como de tipo float .... Aquí, el Código =

Código (cpp) [Seleccionar]

#include <iostream>    
#include <iomanip>      
using namespace std;
int main ()

{

float p = 8.5361478025119782429925187;         // Una Variable de Tipo FLOAT, si no
                                // se le agrega ningún MODIFICADOR
                                // muestra, por defecto,
                                // 4 Cifras significativas ( Decimales )
                                // más 1 de Redondeo
                                  //
                                // Con MODIFICADOR de aumento de
    // Cifras significativas muestra
                              // 7 Cifras


/*double   d  =  3.14159265358979323846264;      // Lo mismo ocurre con una variable de
                              // tipo DOUBLE
      //
  // Con MODIFICADOR de aumento de
  // Cifras significativas muestra
                              // 15 Cifras


long double dd  =  3.14159265358979323846264;  // Lo mismo ocurre con una variable de
                              // tipo LONG DOUBLE
      //
  // Con MODIFICADOR de aumento de
  // Cifras significativas muestra
                              // 19 Cifras */
 
/*cout << f << endl;
/*cout << d << endl;  
cout << dd << endl;*/  

cout.precision(35);
cout << setw(35);
cout << p << endl;
cout << setw(35);
cout << fixed << p << endl;

return 0;

}


Si me pueden ayudar, muchas gracias


MOD: El código debe ir entre etiquetas de Código GeSHi

Loretz

Cada compilador implementa los tipos de punto flotante más o menos igual, aunque hay algunas peculiaridades (como el Visual Studio, que usa la misma representación para double que para long double).

Pueden ver en std::numeric_limits<T>::digits10
https://en.cppreference.com/w/cpp/types/numeric_limits/digits10

Por ejemplo:

Código (cpp) [Seleccionar]
#include <iostream>
#include <numeric>

int main()
{
    std::cout << "cantidad de digitos que pueden representarse sin redondeos ni desbordamiento :\n";
    std::cout << "float: " << std::numeric_limits<float>::digits10 << " digitos significativos.\n";
    std::cout << "double: " << std::numeric_limits<double>::digits10 << " digitos significativos.\n";
    std::cout << "long double: " << std::numeric_limits<long double>::digits10 << " digitos significativos.\n";
}

RayR

La forma en que se representan los números de punto flotante no es específica de C++. En realidad es un estándar que prácticamente todos los lenguajes actuales siguen, aunque puede haber ciertas variaciones.

Primero aclarar que cuando se habla de dígitos significativos, esto se refiere a todos ellos, no sólo la parte decimal (1.25 tiene 3 dígitos significativos). Dicho esto, no se puede hablar de forma exacta de cuántos de ellos en decimal puede almacenar un flotante, ya que esto varía. No basta con ver la cantidad de bits que se usan, ya que los valores que se pueden representar no están distribuidos de manera uniforme. Hay muchos números con 7 dígitos significativos que no se pueden almacenar de forma exacta en precisión simple (float). Por otro lado, hay algunos con más de 7 (como 16777216) que sí se pueden representar perfectamente.

Además de esto, y dado que los flotantes se almacenan en binario, hay números que simplemente no se pueden representar de forma exacta (como 0.1) independientemente del número de bits que se usen.

En cuanto a tus dudas concretas, cuando imprimes el float con cout, de forma predeterminada se usa una precisión de 6 dígitos. Si parece que se están usando menos es simplemente porque el número se está redondeando. Cuando aumentas la precisión de salida, se están mostrando algunos números distintos porque tu número no se pudo guardar de forma exacta en su representación binaria; sólo se almacenó un valor aproximado, y al convertirlo de vuelta a decimal, es posible que el resultado tenga más dígitos que el original. Los detalles exactos son complicados, pero va un ejemplo inventado que puede ayudar a entender esto.

Imagina que quisiéramos almacenar números en forma de fracción, guardando númerador y denominador. Así, 1.5 se podría almacenar como 3/2. Un número como 1.1428 se podría guardar como 8/7, que no es exacto, pero sí una "buena" aproximación. Naturalmente, para convertirlo de vuelta a decimal, dividimos 8 entre 7, pero fíjate que 8/7 es periódico, por lo que la división se podría continuar hasta el infinito. En realidad lo que hacemos es detenernos al llegar a la precisión deseada. Si queremos imprimirlo con 5 dígitos dividimos hasta que tenemos 1.1428. Si en cambio quisiéramos 7 dígitos, obtendríamos 1.142857, y así sucesivamente. No es que el número se esté rellenando con cifras inventadas, es simplemente que lo que guardamos es una aproximación que al convertirse de vuelta nos da más dígitos que el valor original que queríamos almacenar.

Hay más detalles que pueden alterar el valor mostrado, como que al hacer conversiones se podrían utilizar bits adicionales de forma intermedia, y que se pueden aplicar distintos tipos de redondeo, pero creo que ya sería complicarse demasiado y no hace falta para entender de forma general lo que está pasando.

snoopty

#3
Gracias por responder.
Entonces, es definitivo el hecho de que " NO ES CORRECTA " la cantidad de decimales de tipo float ( según la Teoría, 7 decimales ) , double (15) y long double ( 19 ) que se puede mostrar como resultado de una operación ?
Aquí, vuelvo a ejemplificar con otro ejemplo que no hace lo que busco :
DE PASO te consulto porqué aproxima un Tipo int 2.8765 .... a 1 y no a 3 ( 1er cout )

Código (c++) [Seleccionar]

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
   

cout << fixed << setprecision(25) << 2.87654321345868745895256568974i << '\n';
   cout << fixed << setprecision(25) << 2.87654321345868745895256568974f << '\n';
   cout << fixed << setprecision(25) << 2.87654321345868745895256568974 << '\n';
   cout << fixed << setprecision(25) << 2.87654321345868745895256568974f << '\n';
   cout << scientific << setprecision(25) << 2.87654321345868745895256568974d << '\n';

   return 0;
}



/* Gracias por leer el mensasje */

snoopty

Cita de: Loretz en 25 Agosto 2021, 03:13 AM
Cada compilador implementa los tipos de punto flotante más o menos igual, aunque hay algunas peculiaridades (como el Visual Studio, que usa la misma representación para double que para long double).

Pueden ver en std::numeric_limits<T>::digits10
https://en.cppreference.com/w/cpp/types/numeric_limits/digits10

Por ejemplo:

Código (cpp) [Seleccionar]
#include <iostream>
#include <numeric>

int main()
{
    std::cout << "cantidad de digitos que pueden representarse sin redondeos ni desbordamiento :\n";
    std::cout << "float: " << std::numeric_limits<float>::digits10 << " digitos significativos.\n";
    std::cout << "double: " << std::numeric_limits<double>::digits10 << " digitos significativos.\n";
    std::cout << "long double: " << std::numeric_limits<long double>::digits10 << " digitos significativos.\n";
}



Gracias por responder, pero el Código arroja errores, al menos para " Dev-C++ " ;
puede ser que no hayas declarado la const ... static const int digits10; ( o quizás, según versión de C ++ ) static constexpr int digits10 ..... quizá también faltaba la librería " limits " ??????

MinusFour

Cita de: snoopty en 25 Agosto 2021, 06:46 AM
Gracias por responder.
Entonces, es definitivo el hecho de que " NO ES CORRECTA " la cantidad de decimales de tipo float ( según la Teoría, 7 decimales ) , double (15) y long double ( 19 ) que se puede mostrar como resultado de una operación ?

La teoría viene que la mantisa de un número flotante esta determinado por X número de bits. Esto quiere decir que puede representar [latex]2^x[/latex] valores.

Y nosotros lo que queremos es convertir ese número con una base de 10. Para eso podemos decir que:

[latex]2 = 10 ^{\log_{10} 2}[/latex]

El logaritmo base 10 de 2 resuelve la pregunta: "Que potencia de 10 me resulta en 2". Ahora substituyes en [latex]2^x[/latex] y obtienes:

[latex](10^{\log_{10} 2})^x[/latex]

Lo cual puede ser reescrito como:

[latex]10^{x\log_{10} 2}[/latex]

Y aquí podemos ver el número de decimales que podemos obtener:

[latex]x\log_{10} 2[/latex]

Donde x es el número de bits de la mantisa (o su significante).

RayR

Cita de: snoopty en 25 Agosto 2021, 06:46 AM
Gracias por responder.
Entonces, es definitivo el hecho de que " NO ES CORRECTA " la cantidad de decimales de tipo float ( según la Teoría, 7 decimales ) , double (15) y long double ( 19 ) que se puede mostrar como resultado de una operación ?

Así es, no es correcto eso. Me voy a centrar en los float por simplificar, pero esto aplica para los otros tipos.

Lo que pasa es que en muchas páginas utilizan incorrectamente la fórmula log10(224) (el 24 es porque los float tienen un signifcando de 23 bits +  1 implícito), lo cual da 7.22, de lo que se puede suponer que en un float caben todos los números de 7 dígitos y algunos de 8, pero eso es falso, ya que hay números con 7 que no se pueden representar. Incluso, hay algunos flotantes que requieren 9 dígitos decimales para mostrarse correctamente. Y es que esa fórmula sirve para enteros pero no para flotantes, que son más complicados, y no aumentan consecutivamente de unidad en unidad, y hay rangos en los cuales el salto entre números consecutivos es mucho más grande que en otros. Básicamente, con flotantes, esa fórmula nos dice en cuántos dígitos decimales cabe el significando (si lo tratamos como entero), lo cual no es de mucho interés. Lo que queremos es saber los dígitos del número en sí, pero la fórmula no nos dice nada al respecto. Tenemos 224 valores distintos para el significando, sí, pero la cuestión es cómo se distribuyen los números que se pueden representar a partir de ese significando.

Imagina un formato en el cual almacenamos enteros como potencias positivas de 2, usando 4 bits. Así, 64 se almacenaría como 0110, porque 64 = 26. La fórmula nos diría que tenemos 1.20 dígitos, pero el número mayor que podemos almacenar es 215 = 32768, que tiene 5 dígitos. Y es que la fórmula sólo nos dice los dígitos de la potencia almacenada (igual que hace con el significando en los flotantes) pero no del número (de nuevo, como tampoco lo hace con los flotantes y por eso no nos sirve).

En cuanto a tu código, primero, el sufijo 'i' no existe en C++. GCC y compiladores basados en él lo agregan como extensión pero no deberías usarlo porque no es portable. De todas formas, la 'i' indica la parte imaginario de un número complejo, y el programa no sabe cómo interpretarlo. Pon un #include <ccomplex>.

Lo demás es lo que ya te había puesto en mi otro mensaje. No tiene mucho sentido preguntarse cuántos dígitos decimales puede almacenar un flotante. Si acaso, puedes contar con que un float represente correctamente números de hasta 6 dígitos significativos (15 en el caso de double). Arriba de eso, depende. Pero además resulta que "representar correctamente" no necesariamente significa que se almacene de forma exacta, sino que al convertirlo nuevamente a decimal para mostrarse, luego de redondearse, sus primeros 6 dígitos serán iguales al número introducido originalmente. Es lo que pasa con tu código. Lo que se está almacenando son aproximaciones a los números. Sólo se guardan de forma "exacta" los primeros "n" dígitos (de nuevo, ese valor "n" depende del tipo, f ó d, y del número en sí). Si intentas imprimir con una precisión mayor, se muestran dígitos adicionales, que no van a coincidir con los que escribiste. Revisa de nuevo el ejemplo de mi mensaje anterior para ver por qué.

Loretz

Fue mi error, std::numeric_limits está en <limits>

Acá va un ejemplo donde se muestra la capacidad de representación como número decimal en punto flotante de un float, double o long double que garantiza el lenguaje:


Código (cpp) [Seleccionar]
#include <iostream>
#include <iomanip>
#include <limits>
#include <string>

int main()
{
    std::streamsize p_inicial = std::cout.precision(); // precision inicial
    std::cout << "precision inicial = " << p_inicial << "\n\n";
   
    std::string s = "2.99999999999999999999999999999";
    std::cout << "s = " << s << '\n';

    std::cout << std::fixed;
   
    float f = stof(s);
    std::cout << "float " << f << '\n';
    std::cout << std::setprecision(std::numeric_limits<float>::digits10-1); // -1 para la cantidad de decimales
    std::cout << "float: " << std::numeric_limits<float>::digits10 << " digitos significativos.\n";
    f = stof(s.substr(0UI64, std::numeric_limits<float>::digits10+1)); // +1 por el punto decimal
    std::cout << f << "\n\n";
    std::cout << std::setprecision(p_inicial);
   
    double d = stod(s);
    std::cout << "double " << d << '\n';
    std::cout << std::setprecision(std::numeric_limits<double>::digits10-1);
    std::cout << "double: " << std::numeric_limits<double>::digits10 << " digitos significativos.\n";
    d = stod(s.substr(0UI64, std::numeric_limits<double>::digits10+1));
    std::cout << d << "\n\n";
    std::cout << std::setprecision(p_inicial);

    long double ld = stold(s);
    std::cout << "long double " << d << '\n';
    std::cout << std::setprecision(std::numeric_limits<long double>::digits10 - 1);
    std::cout << "long double: " << std::numeric_limits<long double>::digits10 << " digitos significativos.\n";
    ld = stod(s.substr(0UI64, std::numeric_limits<long double>::digits10 + 1));
    std::cout << ld << "\n\n";
    std::cout << std::setprecision(p_inicial);
}


snoopty

Arroja nuevamente error ( para Dev-C++ )
1 ) La sig línea en los tres casos =
/*f = stof(s.substr(0UI64, std::numeric_limits<float>::digits10+1)); // +1 por el punto decimal*/

error :
invalid suffix "UI64" on integer constant


2) rechaza a stod, stof y stold como no declaradas ( previamente ? )