如何在iostreams中生成类似于.NET的'0。###%'的格式?

时间:2014-02-04 12:16:11

标签: c++ iostream

我想输出一个浮点数作为百分比,最多三位小数。

我知道iostream有三种不同的呈现浮动的方式:

  • “默认”,使用fixedscientific的规则显示,具体取决于setprecision定义的有效位数;

  • fixed,显示由setprecision定义的固定小数位数;以及

  • scientific,显示固定数量的小数位,但使用科学记数法,即基数的尾数+指数。

使用以下代码,这三种模式可以是seen in effect

#include <iostream>
#include <iomanip>

int main() {
    double d = 0.00000095;
    double e = 0.95;
    std::cout << std::setprecision(3);
    std::cout.unsetf(std::ios::floatfield);
    std::cout << "d = " << (100. * d) << "%\n";
    std::cout << "e = " << (100. * e) << "%\n";
    std::cout << std::fixed;
    std::cout << "d = " << (100. * d) << "%\n";
    std::cout << "e = " << (100. * e) << "%\n";
    std::cout << std::scientific;
    std::cout << "d = " << (100. * d) << "%\n";
    std::cout << "e = " << (100. * e) << "%\n";
}

// output:
// d = 9.5e-05%
// e = 95%
// d = 0.000%
// e = 95.000%
// d = 9.500e-05%
// e = 9.500e+01%

这些选项都不符合我的要求。

我想在这里避免使用任何科学记法,因为它使百分比真的难以阅读。我想保留最多三个小数位,如果非常小的值显示为零,则可以。但是,我还想避免在上面的0.95这样的情况下在小数位上尾随零:我想要在第二行显示为“95%”。

在.NET中,我可以使用自定义格式字符串来实现这一点,例如“0。###%”,它给出了一个格式为百分比的数字,小数点分隔符的左边至少有一位数字,最多小数点分隔符右边三位数,跳过尾随零:http://ideone.com/uV3nDi

我可以使用iostream实现这一点,而无需编写自己的格式化逻辑(例如特殊的小数字套管)吗?

2 个答案:

答案 0 :(得分:4)

我有理由确定iostreams内置的任何东西都不能直接支持。

我认为处理它的最简洁的方法是在将数字传递给iostream之前将其舍入:

#include <iostream>
#include <vector>
#include <cmath>

double rounded(double in, int places) {
    double factor = std::pow(10, places);

    return std::round(in * factor) / factor;
}

int main() {
    std::vector<double> values{ 0.000000095123, 0.0095123, 0.95, 0.95123 };

    for (auto i : values)
        std::cout << "value = " << 100. * rounded(i, 5) << "%\n";
}

由于它进行舍入的方式,这对它可以使用的数字的大小有限制。对于百分比,这可能不是问题,但 if 你正在处理一个接近最大值的数字,可以在有问题的类型中表示(在这种情况下为double)乘法pow(10, places)可能/会溢出并产生不良结果。

虽然我不能绝对确定,但这似乎不会导致您似乎试图解决的问题出现问题。

答案 1 :(得分:3)

这个解决方案很糟糕。

我很认真。我不喜欢它。它可能很慢,而且函数有一个愚蠢的名字。也许你可以用它进行测试验证,因为它太笨了我想你很容易看到它几乎必须工作。

它还假设小数点分隔符为'.',但不一定是这种情况。适当的点可以通过以下方式获得:

char point = std::use_facet< std::numpunct<char> >(std::cout.getloc()).decimal_point();

但是仍然没有解决问题,因为用于数字的字符可能不同,一般而言,这不应该以这种方式编写。

在这里。

template<typename Floating>
std::string formatFloatingUpToN(unsigned n, Floating f) {
    std::stringstream out;
    out << std::setprecision(n) << std::fixed;
    out << f;

    std::string ret = out.str();

    // if this clause holds, it's all zeroes
    if (std::abs(f) < std::pow(0.1, n))
        return ret;

    while (true) {
        if (ret.back() == '0') {
            ret.pop_back();
            continue;
        } else if (ret.back() == '.') {
            ret.pop_back();
            break;
        } else
            break;
    }

    return ret;
}

here it is in action