如何以下列方式格式化双打?

时间:2015-08-30 23:52:14

标签: c++ c++11 string-formatting

我正在使用C ++,我想以下面明显的方式格式化双打。我尝试使用stringstream玩“固定”和“科学”,但我无法实现这种理想的输出。

double d = -5; // print "-5"
double d = 1000000000; // print "1000000000"
double d = 3.14; // print "3.14"
double d = 0.00000000001; // print "0.00000000001"
// Floating point error is acceptable:
double d = 10000000000000001; // print "10000000000000000"

根据要求,以下是我尝试过的事情:

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

using namespace std;

string obvious_format_attempt1( double d )
{
    stringstream ss;
    ss.precision(15);
    ss << d;
    return ss.str();
}

string obvious_format_attempt2( double d )
{
    stringstream ss;
    ss.precision(15);
    ss << fixed;
    ss << d;
    return ss.str();
}

int main(int argc, char *argv[]) 
{
    cout << "Attempt #1" << endl;
    cout << obvious_format_attempt1(-5) << endl;
    cout << obvious_format_attempt1(1000000000) << endl;
    cout << obvious_format_attempt1(3.14) << endl;
    cout << obvious_format_attempt1(0.00000000001) << endl;
    cout << obvious_format_attempt1(10000000000000001) << endl;

    cout << endl << "Attempt #2" << endl;
    cout << obvious_format_attempt2(-5) << endl;
    cout << obvious_format_attempt2(1000000000) << endl;
    cout << obvious_format_attempt2(3.14) << endl;
    cout << obvious_format_attempt2(0.00000000001) << endl;
    cout << obvious_format_attempt2(10000000000000001) << endl;

    return 0;
}

打印以下内容:

Attempt #1
-5
1000000000
3.14
1e-11
1e+16

Attempt #2
-5.000000000000000
1000000000.000000000000000
3.140000000000000
0.000000000010000
10000000000000000.000000000000000

4 个答案:

答案 0 :(得分:1)

程序无法知道如何以您描述的方式格式化数字,除非您编写一些代码以某种方式分析数字 - 甚至可能非常难。

这里需要知道源代码中的输入格式,一旦编译器将十进制输入源代码转换为二进制形式以存储在可执行文件中,输入格式就会丢失。

可能有效的一种替代方法是输出到stringstream,然后从中修改输出以去除尾随零。像这样:

string obvious_format_attempt2( double d )
{
    stringstream ss;
    ss.precision(15);
    ss << fixed;
    ss << d;
    string res = ss.str();
    // Do we have a dot?
    if ((string::size_type pos = res.rfind('.')) != string::npos)
    {
       while(pos > 0 && (res[pos] == '0' || res[pos] == '.')
       {
           pos--;
       }
       res = res.substr(pos);
    }
    return res;
}

我实际上并没有厌倦它,但作为一个草图,它应该有效。注意事项是,如果你有像0.1这样的东西,它可能打印为0.09999999999999285或其他一些,因为不能以精确的形式表示为二进制。

答案 1 :(得分:1)

准确地格式化二进制浮点数非常棘手,而且传统上是错误的。 1990年在同一期刊上发表的一对论文确定十进制值转换为二进制浮点数并返回可以恢复其值,假设它们不使用比特定约束更多的十进制数字(在C ++中使用{{1表示)适当的类型std::numeric_limits<T>::digits10):

在发布这些论文时,二进制浮点(T"%f""%e")的C格式化指令已经很好地建立,并且它们没有被更改为将新结果考虑在内。这些格式化指令的规范存在的问题是"%g"假设计算小数点后的数字,并且没有格式说明符要求格式化具有一定数字位数的数字,但不一定要开始计数小数点(例如,用小数点格式化但可能有许多前导零)。

格式说明符仍未得到改进,例如,包括另一个用于非科学记法的可能涉及许多零的格式说明符。实际上,Steele / White算法的功能并未完全暴露。遗憾的是,C ++格式化并没有改善这种情况,只是将语义委托给C格式化指令。

设置"%f"并使用精度std::ios_base::fixed的方法是C和C ++标准库提供的浮点格式的最接近的近似值。通过使用std::numeric_limits<double>::digits10格式获取数字,解析结果,然后重写数字,可以获得可以获取的确切格式。为了给这个过程提供一个很好的类似流的接口,可以封装一个std::ios_base::scientific方面。

另一种选择可能是使用Double-Conversion。此实现使用改进的(更快的)算法进行转换。它还公开接口以获取某种形式的数字,但如果我没记错的话,它不会直接作为字符序列。

答案 2 :(得分:0)

您无法执行您想要执行的操作,因为十进制数字不能完全以浮点格式表示。换句话说,double无法精确地保持3.14,它将所有内容存储为2的幂的分数,因此它将其存储为类似3 + 9175/65536或其附近的内容(执行此操作)你的计算器和你得到3.1399993896484375。(我意识到65536不是IEEE双倍的正确分母,但它的要点是正确的。)

这被称为往返问题。你不可靠吗

double x = 3.14;
cout << magic << x;

并获得&#34; 3.14&#34;

如果您必须解决往返问题,请不要使用浮点数。使用自定义&#34;十进制&#34; class,或使用字符串来保存值。

这是您可以使用的十进制类: https://stackoverflow.com/a/15320495/364818

答案 3 :(得分:0)

  

我正在使用C ++,我想用以下显而易见的方式格式化双打。

根据您的样品,我认为您需要

  • 固定而非科学记谱法,
  • 合理(但不是过多)的精确度(这是用户显示,因此可以进行一小部分舍入),
  • 尾随零被截断,
  • 如果数字看起来像一个整数,小数点也会被截断。

以下功能就是这样:

    #include <cmath>
    #include <iomanip>
    #include <sstream>
    #include <string>


    std::string fixed_precision_string (double num) {

        // Magic numbers
        static const int prec_limit = 14;       // Change to 15 if you wish
        static const double log10_fuzz = 1e-15; // In case log10 is slightly off
        static const char decimal_pt = '.';     // Better: use std::locale

        if (num == 0.0) {
            return "0";
        }

        std::string result;

        if (num < 0.0) {
            result = '-';
            num = -num;
        }

        int ndigs = int(std::log10(num) + log10_fuzz);
        std::stringstream ss;
        if (ndigs >= prec_limit) {
            ss << std::fixed
               << std::setprecision(0)
               << num;
            result += ss.str();
        }
        else {
            ss << std::fixed
               << std::setprecision(prec_limit-ndigs)
               << num;
            result += ss.str();
            auto last_non_zero = result.find_last_not_of('0');
            if (result[last_non_zero] == decimal_pt) {
                result.erase(last_non_zero); 
            }
            else if (last_non_zero+1 < result.length()) {
                result.erase(last_non_zero+1);
            }
        }
        return result;
    }

如果您使用的是使用IEEE浮点数的计算机,则将prec_limit更改为16是不可取的。虽然这样可以正确打印0.9999999999999999,但它也打印5.1作为5.0999999999999996和9.99999998作为9.9999999800000001。这是来自我的电脑,您的结果可能因不同的库而有所不同。

prec_limit更改为15是可以的,但仍会导致数字无法“正确”打印。只要您不打算打印1.0-1e-15,指定的值(14)就可以正常工作。

你可以做得更好,但这可能需要丢弃标准库(参见DietmarKühl的回答)。