如何在不使用科学计数法或尾随零的情况下将浮点数输出到cout?

10
在C++中,最优雅的输出浮点数的方式是什么?请勿使用科学计数法或末尾零。
float a = 0.000001f;
float b = 0.1f;

cout << "a: " << a << endl;     //  1e-006 terrible, don't want sci notation.
cout << "b: " << b << endl;     //  0.1 ok.

cout << fixed << setprecision(6);
cout << "a: " << a << endl;     //  0.000001 ok.
cout << "b: " << b << endl;     //  0.100000 terrible, don't want trailing zeros.

这个问题涉及到尾随零,它有帮助吗? https://dev59.com/zGXWa4cB1Zd3GeqPIwII - zero298
它确实解决了同样的问题,但我希望有一个比输出到字符串然后手动替换尾随零更少手动的解决方案。 - Neutrino
@Neutrino:这不是“手动”解决方案,而是代码。如果库支持它,您认为它会有所不同吗?它已经生成了一个字符串,并且必须生成所有数字才能确定最低位数字。编写一次代码,此后与使用库代码没有任何区别。 - Clifford
将双精度浮点数转换为字符串,不使用科学计数法或尾随零,且效率高。 - phuclv
3个回答

6
我不确定什么是“最优雅的方式”,但这里有一种方法。
#include <iostream>
#include <iomanip>
#include <sstream>

using namespace std ;
string fix( float x, int p )
{
    ostringstream strout ;
    strout << fixed << setprecision(p) << x ;
    string str = strout.str() ;
    size_t end = str.find_last_not_of( '0' ) + 1 ;
    return str.erase( end ) ;
}


int main()
{
    float a = 0.000001f ;
    float b = 0.1f ;

    cout << "a: " << fix( a, 6 ) << endl;     //  0.000001 ok.
    cout << "b: " << fix( b, 6 ) << endl;     //  0.1 ok.

   return 0;
}

你可以自己创建输入/输出操纵符(I/O manipulator)来处理此类输出。虽然这种方法更加优雅,但实现方式可能与本文相似。请参考:创建自己的I/O操纵符

有没有办法设置流本身的配置,以便在不必转换或以其他方式操作每个单独插入的情况下,以所需格式输出浮点数? - Neutrino
@Neutrino:如果我知道有一个,我就会发出来了。那有关系吗?你只需要编写一次该函数,然后在任何地方都可以使用它。 - Clifford
设置流的格式配置,使流记住并在所有后续插入相同类型的数据时使用,这是流格式设计的整个理念。反复调用相同的转换函数显然与该设计不符,并且因此远不如优雅。 - Neutrino
@Neutrino:修改流的状态并添加自己的状态标志需要访问其私有成员。我认为你唯一的选择是创建一个继承ostream并重载float的<<运算符的类。这似乎对于这个简单的要求来说太麻烦了。你可以为“float”创建一个别名-“typedef float ffloat”,然后为类型“ffloat”重载<<,它将在除了“ostream::operator<<”之外的所有地方都像普通的“float”一样运行。 - Clifford
typedef并不会创建新的类型,只是现有类型的同义词。因此,如果typedef所引用的类型已经存在一个函数,你就不能为typedef创建函数重载。编译器会将其标记为模糊不清。虽然有一些库声称提供“真正的typedef”功能,但这可能是一个基于你建议的实现的基础。然而,这是一种相当笨重的方法。很可能答案是,在C++中没有优雅的方法来解决这个问题。 - Neutrino
@Neutrino:我猜如果花了三年才发现我的错误,那么它并不是那么关键!你是对的。我错误地认为,即使对于typedef别名,重载分辨率也会考虑类型名称——不确定是什么给了我这个印象。就我个人而言,在任何情况下,我都更喜欢我的原始解决方案;在我的经验中,“持久”的流标志大多是一个麻烦。 - Clifford

3
如果字符串操作不会让你眼花缭乱:
std::string fixedfloat(float x)
{
    std::ostringstream ss;
    ss << std::fixed << std::setprecision(std::cout.precision()) << x;
    std::string str = ss.str();
    return str.substr(0, str.find_last_not_of('0') + 1);
}

int main()
{
    float b = 0.1f;

    std::cout << std::setprecision(6) << fixedfloat(b);
}

或者

class fixedfloat
{
public:
    fixedfloat(float x) : x(x) {}
    float value() const { return x; }

private:
    float x;
};

ostream &operator<<(ostream &out, const fixedfloat &f)
{
    ostringstream ss;
    ss << fixed << setprecision(out.precision()) << f.value();
    string str = ss.str();
    out << str.substr(0, str.find_last_not_of('0') + 1);
    return out;
}

int main()
{
    float b = 0.1f;

    cout << setprecision(6) << fixedfloat(b);
}

第一种方法假定cout是最终目的地,并使用它来匹配所需的精度。第二种方法更好,但需要显式操作流中的每个插入。难道没有办法设置流本身的配置,以便浮点数以所需格式输出,而无需转换或以其他方式操作每个单独的插入吗? - Neutrino
1
这会留下一个尾随的点。 - Niko O

0

另一个和我的例子类似的实际输出为 "200" 或者执行 "200" >> "2"。

这对所有东西都应该有效(因为我将其从我使用的字符串转换为 val 函数)。

string fix(float in) {       
    string s = to_string(in);
    size_t dot = s.find_first_of('.'), last = s.find_last_not_of(".0");

    if (dot!=string::npos) return s.substr(0, max(dot,last+1));
    else return s;
}

如果您首先将cout << fixed;添加到输出流中,则此解决方案可行。 - Markus

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