Imprimer cpp_dec_float en notation scientifique sans zéros à droite
-
21-12-2019 - |
Question
j'utilise cpp_dec_float
pour une précision arbitraire, et c'est génial, mais j'ai du mal à comprendre comment imprimer tous les chiffres significatifs.
Par exemple, avec ce code pour configurer
using boost::multiprecision::cpp_dec_float;
typedef boost::multiprecision::number<cpp_dec_float<100>> mp_type;
mp_type test_num("7.0710678118654752440084436210484903928483593768847403658833986900e-01");
et si j'imprime simplement avec
std::cout << std::scientific << test_num << std::endl;
le résultat est 7.071068e-01
, donc c'est fini.
Si je fais faillite avec
std::cout << std::setprecision(std::numeric_limits<mp_type>::digits) << std::scientific << test_num << std::endl;
Je reçois 7.0710678118654752440084436210484903928483593768847403658833986900000000000000000000000000000000000000e-01
.Je suis content de ne pas perdre en précision, mais ce n'est pas très conservateur d'espace.
Existe-t-il un moyen de supprimer les zéros de fin sans perdre de précision avec les outils existants ?Sinon, comment supprimer les zéros de fin de la chaîne résultante ?
Si les outils existants peuvent être utilisés pour satisfaire mon intention, comment cpp_dec_float
être affiché en notation scientifique sans perte de précision et sans suppression des zéros à droite dans une chaîne ?Je ne trouve que le exemples de flux.
Plus proche
Grâce à mockinterface, je suis beaucoup plus proche.
J'ai changé le code comme ceci :
using boost::multiprecision::cpp_dec_float;
typedef boost::multiprecision::number<cpp_dec_float<0>> mp_type;
mp_type test_num("7.0710678118654752440084436210484903928483593768847403658833986900e-01");
std::cout << test_num.str(0, std::ios_base::scientific) << std::endl;
Avoir une longueur potentiellement illimitée ;cependant, ceci est imprimé :
7.0710678118654752440084436210484903928480e-01
Ce qui est proche mais semble étrange.Dans le source mockinterface me l'a si gracieusement fait remarquer, j'ai trouvé ces lignes
if(number_of_digits == 0)
number_of_digits = cpp_dec_float_total_digits10;
ce qui me suggère qu'il devrait prendre en compte tous les chiffres significatifs, produisant essentiellement ce qui a été saisi en raison de la longueur illimitée.
j'ai vérifié le source pour cpp_dec_float_total_digits10
, et je suis incapable de déterminer exactement de quoi il s'agit ;bien que j'ai trouvé cette section de code qui semble le définir.
private:
static const boost::int32_t cpp_dec_float_elem_digits10 = 8L;
static const boost::int32_t cpp_dec_float_elem_mask = 100000000L;
BOOST_STATIC_ASSERT(0 == cpp_dec_float_max_exp10 % cpp_dec_float_elem_digits10);
// There are three guard limbs.
// 1) The first limb has 'play' from 1...8 decimal digits.
// 2) The last limb also has 'play' from 1...8 decimal digits.
// 3) One limb can get lost when justifying after multiply,
// as only half of the triangle is multiplied and a carry
// from below is missing.
static const boost::int32_t cpp_dec_float_elem_number_request = static_cast<boost::int32_t>((cpp_dec_float_digits10 / cpp_dec_float_elem_digits10) + (((cpp_dec_float_digits10 % cpp_dec_float_elem_digits10) != 0) ? 1 : 0));
// The number of elements needed (with a minimum of two) plus three added guard limbs.
static const boost::int32_t cpp_dec_float_elem_number = static_cast<boost::int32_t>(((cpp_dec_float_elem_number_request < 2L) ? 2L : cpp_dec_float_elem_number_request) + 3L);
public:
static const boost::int32_t cpp_dec_float_total_digits10 = static_cast<boost::int32_t>(cpp_dec_float_elem_number * cpp_dec_float_elem_digits10);
Le nombre de chiffres significatifs peut-il être déterminé et utilisé comme premier argument pour boost::multiprecision::cpp_dec_float::str()
?
La solution
Cela s’est avéré difficile.
La nouvelle est :il n'existe pas une telle fonctionnalité dans cpp_dec_float.Pire encore, cpp_dec_float ne suit pas le nombre de chiffres significatifs qui ont été définis, il n'existe donc pas de moyen « bon marché » de trouver la longueur nécessaire pour imprimer la fraction.
Idées :
Pour certains cas frontaliers (par ex.123.000000000000001) on pourrait prendre le log10 de l'inverse de la partie fractionnaire + log10 de la partie entière.Cela ne parvient absolument pas à être applicable de manière générique.
Si vous souhaitez utiliser les détails d'implémentation, vous pouvez trouver le « dernier élément habité » dans le tableau backend et faire le calcul.Cependant, cela est assez complexe (nécessite de modifier
cpp_dec_float.hpp
et beaucoup de tests).Enfin, j'ai observé que l'implémentation actuelle de
.str()
fait clairement zéro effort pour être efficace.Du tout.
Donc, dans l’ensemble, j’ai les suggestions suivantes.Soit
passer au
gmp
backend (si vous pouvez vous le permettre).Note- ce n'est pas une représentation décimale flottante AFAICT
- cela nécessite qu'une bibliothèque supplémentaire (libgmp) soit liée
gmp_float
fait ont cependant une précision arbitraire, et- c'est
str()
mise en œuvre fait prendre en compte la signification des zéros dans la mantisse
Voir-le En direct sur Coliru
#include <boost/multiprecision/number.hpp> #include <boost/multiprecision/gmp.hpp> #include <iostream> namespace mp = boost::multiprecision; int main() { typedef mp::number<mp::gmp_float<100>> mp_type; mp_type test_num("7.071067811865475244008443621048490392848359376884740365883398690000000000000000000e-01"); std::cout << test_num.str(0, std::ios_base::scientific) << '\n'; }
Impressions
7.071067811865475244008443621048490392848359376884740365883398690e-01
sans autre action requise.Si ce n'est pas une option, je post-traiterais simplement la sortie, en supprimant les zéros de fin :
template <typename T> std::string to_pretty_string(T const& v) { std::string s = v.str(0, std::ios_base::scientific); assert(s.length()>3); // min: 0.e switch (s[0]) { // normalized scientific always has #.####### form of mantissa case '-': case '+': assert(s[2] == '.'); break; default: assert(s[1] == '.'); break; } auto exp = s.find('e'); if (std::string::npos != exp && exp > 0) { for(size_t pos = exp-1; pos; --pos) { if (s[pos] != '0') { // remove run of 0s if applicable s.erase(pos+1, exp-pos-1); break; } } } return std::move(s); }
Voir-le En direct sur Coliru encore
Autres conseils
Vous pouvez indiquer explicitement le nombre de chiffres à afficher avec le cpp_dec_float::str()
méthode:
std::cout << std::scientific << test_num.str(75) << std::endl;
// output: 0.707106781186547524400844362104849039284835937688474036588339869