¿Cómo imprimir un doble de C++ con el número correcto de dígitos decimales significativos?

Cuando se trata de valores de punto flotante en Java, llamando al toString() El método da un valor impreso que tiene el número correcto de cifras significativas de punto flotante. Sin embargo, en C++, imprimir un flotante a través de stringstream redondeará el valor después de 5 dígitos o menos. ¿Hay alguna manera de "imprimir bastante" un flotador en C ++ al número (supuesto) correcto de cifras significativas?


EDITAR: Creo que me están malinterpretando. Quiero que la salida tenga una longitud dinámica, no una precisión fija. Estoy familiarizado con setprecision. Si observa la fuente de Java para Double, calcula la cantidad de dígitos significativos de alguna manera, y realmente me gustaría entender cómo funciona y/o qué tan factible es replicar esto fácilmente en C++.

/*
 * FIRST IMPORTANT CONSTRUCTOR: DOUBLE
 */
public FloatingDecimal( double d )
{
    long    dBits = Double.doubleToLongBits( d );
    long    fractBits;
    int     binExp;
    int     nSignificantBits;

    // discover and delete sign
    if ( (dBits&signMask) != 0 ){
        isNegative = true;
        dBits ^= signMask;
    } else {
        isNegative = false;
    }
    // Begin to unpack
    // Discover obvious special cases of NaN and Infinity.
    binExp = (int)( (dBits&expMask) >> expShift );
    fractBits = dBits&fractMask;
    if ( binExp == (int)(expMask>>expShift) ) {
        isExceptional = true;
        if ( fractBits == 0L ){
            digits =  infinity;
        } else {
            digits = notANumber;
            isNegative = false; // NaN has no sign!
        }
        nDigits = digits.length;
        return;
    }
    isExceptional = false;
    // Finish unpacking
    // Normalize denormalized numbers.
    // Insert assumed high-order bit for normalized numbers.
    // Subtract exponent bias.
    if ( binExp == 0 ){
        if ( fractBits == 0L ){
            // not a denorm, just a 0!
            decExponent = 0;
            digits = zero;
            nDigits = 1;
            return;
        }
        while ( (fractBits&fractHOB) == 0L ){
            fractBits <<= 1;
            binExp -= 1;
        }
        nSignificantBits = expShift + binExp +1; // recall binExp is  - shift count.
        binExp += 1;
    } else {
        fractBits |= fractHOB;
        nSignificantBits = expShift+1;
    }
    binExp -= expBias;
    // call the routine that actually does all the hard work.
    dtoa( binExp, fractBits, nSignificantBits );
}

Después de esta función, llama dtoa( binExp, fractBits, nSignificantBits ); que maneja un montón de casos - esto es de OpenJDK6


Para mayor claridad, un ejemplo: Java:

double test1 = 1.2593;
double test2 = 0.004963;
double test3 = 1.55558742563;
    
System.out.println(test1);
System.out.println(test2);
System.out.println(test3);

Salida:

1.2593
0.004963
1.55558742563

C ++:

std::cout << test1 << "\n";
std::cout << test2 << "\n";
std::cout << test3 << "\n";

Salida:

1.2593
0.004963
1.55559

preguntado el 27 de julio de 12 a las 21:07

La opción toString método no da el "número correcto" de cifras significativas; no tiene forma de saber cuáles de las cifras son significativas. Los números de punto flotante IEEE no representan esa información. -

Como referencia, aquí hay un enfoque común de Java para Personalización de formatos. -

Java hace una serie de cálculos sobre el valor doble al imprimir; simplemente no tengo los conocimientos suficientes para entender lo que está haciendo. Ver editar -

@Dave Sí, lo están malinterpretando, un problema con SO es que las personas pueden estar ansiosas por obtener su respuesta primero, a veces a expensas de comprender lo que se pregunta. Yo mismo he sido culpable de esto. -

4 Respuestas

¿Hay alguna manera de "imprimir bastante" un flotador en C ++ al número (supuesto) correcto de cifras significativas?

Sí, puedes hacerlo con C++20 std::format, Por ejemplo:

double test1 = 1.2593;
double test2 = 0.004963;
double test3 = 1.55558742563;
std::cout << std::format("{}", test1) << "\n";
std::cout << std::format("{}", test2) << "\n";
std::cout << std::format("{}", test3) << "\n";

huellas dactilares

1.2593
0.004963
1.55558742563

El formato predeterminado le dará la representación decimal más corta con una garantía de ida y vuelta como en Java.

Dado que esta es una característica nueva y es posible que algunas bibliotecas estándar aún no la admitan, puede usar la biblioteca {fmt}, std::format está basado en. {fmt} también proporciona print función que lo hace aún más fácil y eficiente (Godbolt):

fmt::print("{}", 1.2593);

Aviso legal: Soy el autor de {fmt} y C ++ 20 std::format.

Respondido el 20 de diciembre de 20 a las 16:12

Creo que está hablando de cómo imprimir el número mínimo de dígitos de coma flotante que le permiten leer exactamente el mismo número de coma flotante. Este documento es una buena introducción a este complicado problema.

http://grouper.ieee.org/groups/754/email/pdfq3pavhBfih.pdf

La función dtoa se parece al trabajo de David Gay, puede encontrar la fuente aquí http://www.netlib.org/fp/dtoa.c (aunque esto es C no Java).

Gay también escribió un artículo sobre su método. No tengo un enlace, pero se hace referencia en el documento anterior, por lo que probablemente pueda buscarlo en Google.

Respondido 27 Jul 12, 23:07

¡Gracias! Siento que fuiste la única persona que entendió lo que estaba diciendo. - Dave

El enlace al periódico parece que ya no funciona. Me pregunto si es "Cómo leer con precisión números de punto flotante" de William D Clinger. - MM

Puede usar la técnica ios_base::precision donde puede especificar la cantidad de dígitos que desea

Por ejemplo:

#include <iostream>
using namespace std;

int main () {
double f = 3.14159;
cout.unsetf(ios::floatfield);            // floatfield not set
cout.precision(5);
cout << f << endl;
cout.precision(10);
cout << f << endl;
cout.setf(ios::fixed,ios::floatfield);   // floatfield set to fixed
cout << f << endl;
return 0;

El código anterior con salida
3.1416
3.14159
3.1415900000

Respondido 27 Jul 12, 21:07

Lo que estoy tratando de evitar son los ceros finales en su ejemplo. No quiero un valor fijo para mi salida. - Dave

Hay una utilidad llamada numeric_limits:

#include <limits>

    ...
    int num10 = std::numeric_limits<double>::digits10;
    int max_num10 = std::numeric_limits<double>::max_digits10;

Tenga en cuenta que los números IEEE no están representados exactamente por dígitos decimales. Estas son cantidades binarias. Un número más preciso es el número de bits binarios:

    int bits = std::numeric_limits<double>::digits;

Para imprimir todos los dígitos significativos, use setprecision con esto:

out.setprecision(std::numeric_limits<double>::digits10);

Respondido 28 Jul 12, 04:07

esto no es lo que estoy buscando - Dave

Bien, lo entiendo, creo. ¿Quiere números que rastreen la precisión desde la construcción hasta las operaciones numéricas? El estándar C++ no tiene esto. Hay esfuerzos para agregar números de precisión múltiple. Creo que su requisito es razonable para tales números. Básicamente, los números fundamentales de coma flotante simplemente no tienen la inteligencia para esto y nunca la tendrán. - emsr

Miren esto: holoborodko.com/pavel/mpfr. Creo que esto es lo que quieres. Buena suerte. - emsr

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.