web-dev-qa-db-fra.com

Comment "calculer" le nombre correct de décimales d'une valeur double?

J'ai besoin d'aide pour conserver la précision d'une double. Si j'attribue un littéral à un double, la valeur réelle est tronquée.

int main() {
    double x = 7.40200133400;
    std::cout << x << "\n";
}

Pour l'extrait de code ci-dessus, la sortie était 7.402
Y at-il un moyen d’empêcher ce type de troncature? Ou existe-t-il un moyen de calculer exactement combien de points flottants pour un double? Par exemple, number_of_decimal(x) donnerait 11, puisque l'entrée est inconnue au moment de l'exécution, je ne peux donc pas utiliser setprecision().


Je pense que je devrais changer ma question en: Comment convertir un double en chaîne sans tronquer les points flottants. c'est à dire. 

#include <iostream>
#include <string>
#include <sstream>

template<typename T>
std::string type_to_string( T data ) {
    std::ostringstream o;
    o << data;
    return o.str();
}

int main() {
    double x = 7.40200;
    std::cout << type_to_string( x ) << "\n";
}

La production attendue devrait être de 7.40200 mais le résultat réel était de 7.402. Alors, comment puis-je contourner ce problème? Une idée?

26
Chan

Étant donné que les variables float et double sont stockées en interne dans un fichier binaire, le _7.40200133400 littéral correspond en fait au numéro 7.402001334000000376533989765448575765657881145187

... alors quelle précision voulez-vous vraiment? :-)

#include <iomanip>    
int main()
{
    double x = 7.40200133400;
    std::cout << std::setprecision(51) << x << "\n";
}

Et oui, ce programme imprime vraiment 7.40200200343400000037653398976544849574565887451171875!

28
fredoverflow

Vous devez utiliser setiosflags(ios::fixed) et setprecision(x).

Par exemple, cout << setiosflags(ios::fixed) << setprecision(4) << myNumber << endl;

De plus, n'oubliez pas de #include <iomanip.h>.

17
Maxpm
std::cout << std::setprecision(8) << x;

Notez que setprecision est persistant et que tous les flottants suivants que vous imprimez seront imprimés avec cette précision, jusqu'à ce que vous changiez la valeur. Si cela pose un problème et que vous souhaitez contourner ce problème, vous pouvez utiliser un objet proxy stringstream:

std::stringstream s;
s << std::setprecision(8) << x;
std::cout << s.str();

Pour plus d'informations sur le formatage iostream, consultez la section Manipulateurs d'entrée/sortie dans cppreference.

9
Kos

Solution utilisant Boost.Format :

#include <boost/format.hpp>
#include <iostream>

int main() {
    double x = 7.40200133400;
    std::cout << boost::format("%1$.16f") % x << "\n";
}

Ceci génère 7.4020013340000004.

J'espère que cela t'aides!

5
Daniel Lidström

La seule réponse à cette question que j’ai trouvée est qu’il n’ya aucun moyen de le faire correctement (comme dans le calcul des décimales)! La raison principale en est que la représentation d'un nombre peut ne pas correspondre à ce que vous attendez. Par exemple, 128.82 semble assez inoffensif, mais sa représentation réelle est 128.8199999999 ... comment calculer le nombre de décimales ??

3
Nim

Les doubles n'ont pas ont décimales. Ils ont des places binaires. Et les places binaires et les décimales sont incommensurables (car log2(10) n'est pas un entier).

Ce que vous demandez n'existe pas.

2
user207421

Répondre à votre réponse-édition: Il n'y a aucun moyen de le faire. Dès que vous affectez une valeur à une double, tous les zéros de fin sont perdus (pour le compilateur/ordinateur, 0.402, 0.4020 et 0.40200 sont identiques). La seule façon de conserver les zéros à la fin, comme vous l'avez indiqué, consiste à stocker les valeurs sous forme de chaînes (ou à effectuer des tromperies dans lesquelles vous gardez une trace du nombre de chiffres que vous aimez et le formatez à cette longueur).

2
Mark B

Faisons une requête analogue: après avoir initialisé un entier avec 001, vous voudrez l’imprimer avec les zéros au début. Ces informations de formatage n'ont tout simplement jamais été stockées.

Pour mieux comprendre le stockage en virgule flottante double précision, reportez-vous à la norme IEEE 754.

1
fned

La deuxième partie de la question, relative à la préservation des zéros de fin dans une valeur à virgule flottante de la spécification de la valeur au résultat en sortie, n'a pas de solution. Une valeur en virgule flottante ne conserve pas la spécification d'origine. Il semble que cette partie insensée ait été ajoutée par un modérateur SO.

En ce qui concerne la première partie de la question, que j’interprète comme présentant tous les chiffres significatifs de 7.40200133400, c’est-à-dire avec une sortie telle que 7.402001334, vous pouvez simplement supprimer les zéros de fin d’un résultat en sortie qui ne contient que des chiffres fiables dans la valeur double:

#include <assert.h>         // assert
#include <limits>           // std::(numeric_limits)
#include <string>           // std::(string)
#include <sstream>          // std::(ostringstream)

namespace my{
    // Visual C++2017 doesn't support comma-separated list for `using`:
    using std::fixed; using std::numeric_limits; using std::string;
    using std::ostringstream;

    auto max_fractional_digits_for_positive( double value )
        -> int
    {
        int result = numeric_limits<double>::digits10 - 1;
        while( value < 1 ) { ++result; value *= 10; }
        return result;
    }

    auto string_from_positive( double const value )
        -> string
    {
        ostringstream stream;
        stream << fixed;
        stream.precision( max_fractional_digits_for_positive( value ) );
        stream << value;
        string result = stream.str();
        while( result.back() == '0' )
        {
            result.resize( result.size() - 1 );
        }
        return result;
    }

    auto string_from( double const value )
        -> string
    {
        return (0?""
            : value == 0?   "0"
            : value < 0?    "-" + string_from_positive( -value )
            :               string_from_positive( value )
            );
    }
}

#include<iostream>
auto main()
    -> int
{
    using std::cout;
    cout << my::string_from( 7.40200133400 ) << "\n";
    cout << my::string_from( 0.00000000000740200133400 ) << "\n";
    cout << my::string_from( 128.82 ) << "\n";
}

Sortie:

 7.402001334 
 0.000000000007402001334 
 128.81999999999999 

Vous pouvez envisager d'ajouter une logique d'arrondi pour éviter les longues séquences de 9, comme dans le dernier résultat.

0