Print cpp_dec_float in scientific notation without

2019-07-07 08:51发布

I'm using cpp_dec_float for arbitrary precision, and it's great, but I'm having trouble figuring out how to print all significant digits.

For example, with this code to setup

using boost::multiprecision::cpp_dec_float;
typedef boost::multiprecision::number<cpp_dec_float<100>> mp_type;

mp_type test_num("7.0710678118654752440084436210484903928483593768847403658833986900e-01");

and if I simply print with

std::cout << std::scientific << test_num << std::endl;

the result is 7.071068e-01, so that's out.

If I go for broke with

std::cout << std::setprecision(std::numeric_limits<mp_type>::digits) << std::scientific << test_num << std::endl;

I get 7.0710678118654752440084436210484903928483593768847403658833986900000000000000000000000000000000000000e-01. I'm happy not to lose the precision, but it's not very space conservative.

Is there a way to remove the trailing zeros without losing any precision with existing tools? If not, how can the trailing zeros be removed from the resultant string?

If existing tools can be used to satisfy my intent, how can cpp_dec_float be output in scientific notation without lost precision and trailing zeros removed to a string? I can only find the stream examples.

Closer

Thanks to mockinterface, I'm much closer.

I've changed the code to this:

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;

To have potentially unlimited length; however, this is printed:

7.0710678118654752440084436210484903928480e-01

Which is close but seems strange. In the source mockinterface so graciously pointed out to me, I found these lines

if(number_of_digits == 0)
    number_of_digits = cpp_dec_float_total_digits10;

which suggests to me that it should take into account all significant digits, basically outputting what was input because of the unlimited length.

I checked the source for cpp_dec_float_total_digits10, and I am unable to determine exactly what it is; although, I did find this code section that seems to define it.

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);

Can the number of significant digits be determined and used as the first argument for boost::multiprecision::cpp_dec_float::str()?

2条回答
爱情/是我丢掉的垃圾
2楼-- · 2019-07-07 09:02

You can explicitly state the number of digits you need to be output with the cpp_dec_float::str() method:

std::cout << std::scientific << test_num.str(75) << std::endl;
// output: 0.707106781186547524400844362104849039284835937688474036588339869
查看更多
Rolldiameter
3楼-- · 2019-07-07 09:12

This turned out to be a tough one.

The short story is: there is no such functionality in cpp_dec_float. What's worse, cpp_dec_float doesn't track the number of significant digits that have been set, so there's no "cheap" way to find the length needed to print the fraction.

Ideas:

  • For some border cases (e.g. 123.000000000000001) one could take the log10 of the reciprocal of the the fractional part + log10 of the integer part. This completely fails to be generically applicable.

  • If you want to use implementation details you might find the 'last inhabited' element in the backend array, and do the maths. However, this is pretty involved (requires modifying cpp_dec_float.hpp and a lot of testing).

  • Finally, I observed that the current implementation for .str() clearly makes zero effort to be efficient. At all.

So all in all I have the following suggestions. Either

  1. switch to the gmp backend (if you can afford it). Note

    • this is not a decimal float representation AFAICT
    • this requires an additional library (libgmp) to be linked
    • gmp_float does have arbitrary precision though, and
    • it's str() implementation does take into account the significance of zeroes in the mantissa

    See it Live On 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';
    }
    

    Prints 7.071067811865475244008443621048490392848359376884740365883398690e-01 without further actions required.

  2. If that's not an option, I'd just post-process the output, removing the trailing zeroes:

    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);
    }
    

See it Live On Coliru again

查看更多
登录 后发表回答