C++流实现双精度指数的便携式打印

15
我想要以一种跨平台的方式将一个双精度值输出到std::cout。希望在所有平台上输出都相同。
我遇到了指数格式的问题。 以下是代码:
#include <iostream>
int main()
{
    std::cout << 0.1e-7 << std::endl;
    return 0;
}

使用GCC编译后得到了这个输出:

1e-08

以下是在MSVC下的输出结果

1e-008
如何使两个输出结果相同?
如果这是一个愚蠢的问题我很抱歉,但到目前为止我还没有找到答案。所有格式似乎都围绕着小数点前的所有内容进行格式化...
编辑:GCC的输出为1e-08而不是1e-8(最初声明的),因此它是符合规范的。对于造成的混淆,我很抱歉。
编辑2:根据Dietmar的提醒,实际上将“尾数”重命名为“指数”。维基百科上也有一节关于尾数与有效数字的区别

你看过C++操作符了吗? - razlebe
1
@razlebe:我在使用操作器时找不到答案。 - Manuel
我认为GCC不一致,因为它打印1.e-081.e-18(两位数字),但是它打印1.e-256(三位数字)。我找不到一个流库来解决这个问题(当然我试过iostream和Boost.Format)。所以如果想要固定宽度的双精度浮点数,就需要为指数可能出现的第三个数字预留额外的空间。 - alfC
3个回答

11

没有任何操纵器来控制指数的格式(我假设你是指指数而不是尾数;另外,官方用于表示尾数的名称是significant)。更糟糕的是,我看不到C标准中限制指数格式的规则。我意识到这是关于C ++的问题,但为了格式详细信息的目的,C ++标准参考C标准。

我知道的唯一方法是使用自己的std::num_put<char>刻度,将值格式化为所需的格式。然后,将此facet放入一个std::locale,该locale再imbue()到std::cout中。潜在的实现可以使用默认的std::num_put<char>刻度(或者不幸的是,可能更简单的snprintf())来格式化浮点数,然后从指数中删除前导零。


5
C++ 的格式化是基于 printf 定义的,其中规定“指数部分必须至少包含两个数字”(因此 g++ 不符合标准)。这里我正在阅读 POSIX 标准,该标准应符合 C 标准。但我记得有些文字说,除非必要,否则指数部分不能超过两个字符(这也使 VC 错误);我记得很久以前曾经讨论过这个问题,并且已经确定 VC 不符合标准(如果您必须使用这个编译器,则无法解决这个问题)。 - James Kanze
1
@JamesKanze 这是否可能被解释为他们意味着“字符”而不是“数字”,在这种情况下减号将计数? - Flexo
我只是快速浏览了C99标准(我没有更新版本),并没有找到任何指数格式规则。无论如何,无论符合还是不符合,似乎都有一定的实现自由,并且实现中肯定存在差异。我仍然认为上述描述的方法可能是最简单的透明更改所产生的格式的方法。 - Dietmar Kühl
1
C90(在fprintf的描述中,%eE):“指数始终包含至少两位数字。”,C99:“指数始终包含至少两位数字,并且只包含必要的额外数字来表示指数。”而在C11中(或称为n1548,我没有找到一份负担得起的C11版本),与C99具有相同的措辞。 - AProgrammer
1
@AProgrammer C90听起来就像我记得的(而且我的Posix标准读物中的版本似乎对应C90)。C++标准(至少到C++03)以C90标准为基础,所以我猜VC++是符合标准的(而VC没有声称符合任何C标准)。 - James Kanze
显示剩余3条评论

3
虽然Dietmar的答案是简洁且可能是唯一真正可移植的答案,但我意外地找到了一个快速而粗略的答案:MSVC提供_set_output_format函数,您可以使用该函数切换为“将指数打印为两个数字”的模式。
以下RAII类可以在您的main()函数中实例化,以获得与GCC、CLANG和MSVC相同的行为。
class ScientificNotationExponentOutputNormalizer
{
public:
    unsigned _oldExponentFormat;

    ScientificNotationExponentOutputNormalizer() : _oldExponentFormat(0)
    {
#ifdef _MSC_VER
        // Set scientific format to print two places.
        unsigned _oldExponentFormat = _set_output_format(_TWO_DIGIT_EXPONENT);
#endif
    }

    ~ScientificNotationExponentOutputNormalizer()
    {
#ifdef _MSC_VER
        // Enable old exponent format.
        _set_output_format(_oldExponentFormat);
#endif
    }
};

这需要至少MSVCR80.dll,而将其设置为与MinGW一起工作非常麻烦。 - Manuel
注意:根据文档中所述,“此函数已过时。从Visual Studio 2015开始,在CRT中不再可用。” - BenC

2
问题在于Visual C++没有遵循C99标准。在Visual C++ 2015中,由于编译器现在遵循标准,因此删除了_set_output_format函数:

%e%E格式说明符将浮点数格式化为十进制尾数和指数的形式。 %g%G格式说明符在某些情况下也以这种方式格式化数字。 在以前的版本中,CRT总是生成带有三位数字指数的字符串。例如,printf("%e\n", 1.0) 将打印1.000000e+000这是不正确的:如果指数可以使用一个或两个数字表示,则只打印两个数字

在Visual Studio 2005中添加了全局一致性开关:_set_output_format。程序可以使用参数_TWO_DIGIT_EXPONENT调用此函数以启用符合规范的指数打印。 默认行为已更改为符合标准的指数打印模式

请参见Visual C++ 2015中的Breaking Changes。对于旧版本,请参见@Manuel的答案。
FYI,在C99标准中,我们可以读到:

e,E

表示浮点数的双精度参数以[-]d.ddd e(+-)dd的形式转换,其中小数点字符前有一个数字(如果参数不为零,则为非零),其后的数字个数等于精度;如果未指定精度,则将其视为6;如果精度为零且未指定#标志,则不会出现小数点字符。该值四舍五入到适当数量的数字。 E转换说明符产生一个具有E而不是e引入指数的数字。 指数始终包含至少两位数字,只需要更多数字来表示指数。如果值为零,则指数为零。表示无穷大或NaN的双精度参数以f或F转换说明符的形式转换。

这与没有给出所需指数长度的C90有所不同。
请注意,最近的Visual C++更改还涉及如何打印naninf等。

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