以科学计数法打印 cpp_dec_float,不带尾随零

Posted

技术标签:

【中文标题】以科学计数法打印 cpp_dec_float,不带尾随零【英文标题】:Print cpp_dec_float in scientific notation without trailing zeros 【发布时间】:2014-05-26 09:58:39 【问题描述】:

我正在使用cpp_dec_float 来获得任意精度,这很好,但我无法弄清楚如何打印所有有效数字。

例如,用这个代码来设置

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

mp_type test_num("7.0710678118654752440084436210484903928483593768847403658833986900e-01");

如果我只是打印

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

结果是7.071068e-01,所以就出来了。

如果我分手了

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

我收到7.0710678118654752440084436210484903928483593768847403658833986900000000000000000000000000000000000000e-01。我很高兴没有丢失精度,但它不是很保守。

有没有一种方法可以在不损失现有工具的任何精度的情况下删除尾随零?如果不是,如何从结果字符串中删除尾随零?

如果可以使用现有工具来满足我的意图,如何以科学计数法输出cpp_dec_float 而不会丢失精度并将尾随零删除到字符串中?我只能找到stream examples。

更接近

多亏了 mockinterface,我离得更近了。

我已将代码更改为:

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;

具有潜在的无限长度;但是,这是打印出来的:

7.0710678118654752440084436210484903928480e-01

这很接近但看起来很奇怪。在source 模拟界面中如此亲切地向我指出,我发现了这些行

if(number_of_digits == 0)
    number_of_digits = cpp_dec_float_total_digits10;

这向我建议它应该考虑所有有效数字,由于长度不受限制,基本上输出输入的内容。

我检查了source 中的cpp_dec_float_total_digits10,但我无法确定它到底是什么;不过,我确实找到了似乎定义它的代码部分。

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

是否可以确定有效位数并将其用作boost::multiprecision::cpp_dec_float::str() 的第一个参数?

【问题讨论】:

【参考方案1】:

您可以使用cpp_dec_float::str() 方法明确说明需要输出的位数:

std::cout << std::scientific << test_num.str(75) << std::endl;
// output: 0.707106781186547524400844362104849039284835937688474036588339869

【讨论】:

谢谢!嗯,我可能不知道我应该有多少位数。另外,科学计数法的结果如何存储在字符串中?非常感谢您! 对于科学记数法试试std::string s = test_num.str(7, std::ios_base::scientific);,如果你不知道你会有多少位数,你就会陷入困境,你必须提前决定位数您将始终显示,因为cpp_dec_float 会进行自己的格式化。当然,您总是可以将结果放在double 中,然后任意格式化。 感谢您向我展示如何将结果转换为string!有没有一种高效的方法可以在e 之前简单地删除0s?我可以以一种菜鸟的方式做到这一点,但string 解析总是让我紧张,因为我总是设法扼杀性能,哈哈。非常感谢您! 恐怕没有简单的方法,但是从e 跳回零直到遇到非零的简单循环应该不难实现。此外,一旦您看到 cpp_dec_float 在其字符串格式中的可疑提取(参见 code.woboq.org/boost/boost/boost/multiprecision/…),您将学会停止担心性能并开始爱上 lexical_cast。 @Gracchus 您意识到,由于您使用的是 Boost Multiprecision,这是一个仅限标头的库,因此根据定义,您的源文件中包含此源代码?【参考方案2】:

结果证明这是一个艰难的过程。

简而言之:cpp_dec_float 中没有这样的功能。更糟糕的是,cpp_dec_float 不跟踪已设置的有效位数,因此没有“便宜”的方法来找到打印分数所需的长度。

想法:

对于某些边界情况(例如 123.000000000000001),可以取小数部分的倒数的 log10 + 整数部分的 log10。这完全不能普遍适用。

如果您想使用实现细节,您可能会在后端数组中找到“last inhabited”元素,然后进行数学计算。但是,这涉及很多(需要修改 cpp_dec_float.hpp 并进行大量测试)。

最后,我观察到.str() 的当前实现显然使努力提高效率。完全没有。

总而言之,我有以下建议。要么

    切换到gmp 后端(如果你负担得起的话)。注意

    这不是十进制浮点数表示 AFAICT 这需要链接一个额外的库 (libgmp) gmp_float 确实具有任意精度, str() 实现确实考虑了尾数中零的重要性

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

    无需进一步操作即可打印7.071067811865475244008443621048490392848359376884740365883398690e-01

    如果这不是一个选项,我只需对输出进行后处理,删除尾随零:

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

再次查看Live On Coliru

【讨论】:

完美,非常感谢!我将同时使用这两个来起飞最后一个0。 ;)) 我不知道字符串构造函数可以与gmp 一起使用,所以再次感谢您!我要看看它是否适用于 MPFR,因为它具有“正确的舍入”。 mpfr.org 我很高兴能使用更快的库!我希望我能给你更多的支持!非常感谢! @sehe 在 RVO 上是 std::move 重要吗? 很抱歉回到这个问题,但是在小数点后只有0s 的随机机会中,小数点如何也可以删除? coliru.stacked-crooked.com/a/c84d868316b6ae8d 再次感谢您提供这么棒的代码!我对 c++ 很陌生,我什至无法确定你做了什么来删除 0s,哈哈。 @Gracchus 你的意思是this?但警告:您必须注意与区域设置相关的小数分隔符(可能是 , 或其他完全不同的东西) @mockinterface 通常不是,但我认为如果您想通过移动返回(例如,对于仅移动类型),MSVC 需要它。

以上是关于以科学计数法打印 cpp_dec_float,不带尾随零的主要内容,如果未能解决你的问题,请参考以下文章

将数据帧转换为 numpy 数组会导致所有数字以科学计数法打印 [关闭]

以正常形式打印浮点数,而不是指数形式/科学记数法[重复]

以非科学计数法打印 float 或 double

用于 ggplot2 的不带科学记数法的 R 标记中的 cut 函数

科学计数法中e和E的区别

Python decimal.Decimal 以科学计数法产生结果