在C++中格式化n个有效数字而不使用科学计数法

11

我希望将浮点数值格式化为n个有效数字,但永远不使用科学计数法(即使它会更短)。

格式规范%f不涉及有效数字,%g有时会给出科学计数法(这在我的使用中是不合适的)。

我希望值以 "123"、"12.3"、"1.23" 或 "0.000000123" 的形式呈现。

是否有一种优雅的方式可使用C++或Boost实现此目的?


那么你想要将123456打印为123000吗? - Mats Petersson
@MatsPetersson,是的,这就是我要说的(但实际上我正在将百分比值格式化为3个有效数字,因此不会出现问题)。 - PeteC
可能是个好方法,但我还没有测试过:https://dev59.com/k2Up5IYBdhLWcg3wmYct#15167203 - Gauthier Boaglio
3个回答

13

我知道的最好方法(并且在我的代码中使用它)是

#include <string>
#include <math.h>
#include <sstream>
#include <iomanip>

int round(double number)
{
    return (number >= 0) ? (int)(number + 0.5) : (int)(number - 0.5);
}

std::string format(double f, int n)
{
    if (f == 0) {
        return "0";
    }            
    int d = (int)::ceil(::log10(f < 0 ? -f : f)); /*digits before decimal point*/
    double order = ::pow(10., n - d);
    std::stringstream ss;
    ss << std::fixed << std::setprecision(std::max(n - d, 0)) << round(f * order) / order;
    return ss.str();
}

c++11已经有std::round了,因此您在新编译器中将不需要我的这个版本。

我正在利用的技巧是通过取基数为10的对数来计算小数点前的数字位数,并将其从所需精度中减去,以获得所需的精度。

它也满足@Mats Petersson的要求,因此可以在所有情况下使用。

我不喜欢的一点是对零的初始检查(以使对数函数不崩溃)。欢迎改进建议/直接编辑本答案。


相當不優雅,但到目前為止最接近的解法!謝謝。但仍會以科學記號印出:format(0.00000123456, 3) 印出 1.23e-06 - PeteC
好的,试试我的修改后的答案。Pint说它很完美(但由于精度最大化,现在甚至更不优雅了)。 - Bathsheba
看起来不错,非常感谢。整个问题令人沮丧,因为“有效数字”的逻辑在标准库中显然存在,但与S.N.方面密不可分。 - PeteC
在我看来,这已经足够优雅了 :) 谢谢。 - Gauthier Boaglio
2
这里有一个问题(我将尝试自己解决):如果_f_是十的幂并且_n_> log10 _f_,则输出将在结尾多出一个零。例如,format(100.0, 3)是“100.0”,而实际上应该是“100”(或者理想情况下为“100.”,带有尾数小数点)。 - bdesham
1
针对@bdesham指出的问题,使用floor(..) + 1而不是ceil(..)可以正确格式化十的幂次方。 - Alexis Leclerc

0

std::fixedstd::setprecision(以及一般的 <iomanip>)是你的好朋友。

std::cout << 0.000000123 << '\n';

打印 1.23e-07

std::cout << std::setprecision(15) << std::fixed << 0.000000123 << '\n';

打印 0.000000123000000

只需记住浮点数具有有限的精度,因此

std::cout << std::fixed << 123456789012345678901234567890.0 << '\n';

将会打印出123456789012345677877719597056.000000(可能不是您想要的)


4
两者都不是我想要的。"1.23e-07" 是标准形式,我想避免使用它。"0.000000123000000" 是固定小数点位数的数字,而我需要一个固定有效数字位数的数字。 - PeteC

0

我认为你需要自己去除尾随的零:

string trimString(string str)
{
    string::size_type s;
    for(s=str.length()-1; s>0; --s)
    {
        if(str[s] == '0') str.erase(s,1);
        else break;
    }
    if(str[s] == '.') str.erase(s,1);
    return str;
}

使用方法:

double num = 0.000000123;
stringstream ss;
ss << num;
ss.str("");
ss << std::setprecision(15) << std::fixed << num; // outputs 0.000000123000000
string str;
ss >> str;
str = trimString(str);
cout << str << endl;  // outputs 0.000000123

组合:

string format(int prec, double d) {
    stringstream ss;
    ss << d;
    ss.str("");
    ss << std::setprecision(prec) << std::fixed << d;

    string str;
    ss >> str;
    string::size_type s;
    for(s=str.length() - 1; s > 0; --s)
    {
        if(str[s] == '0') str.erase(s,1);
        else break;
    }
    if(str[s] == '.') str.erase(s,1);
    return str;
}

使用方法:
double num = 0.000000123;
cout << format(15, num) << std::endl;

如果有人知道更好的方法...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接