Portable printing of exponent of a double to C++ iostreams

让人想犯罪 __ 提交于 2020-06-24 07:19:11

问题


I want to print a double value to std::cout portably (GCC, clang, MSVC++) such that the output is the same on all platforms.

I have a problem with the formatting of the exponent. The following program

#include <iostream>
int main()
{
    std::cout << 0.1e-7 << std::endl;
    return 0;
}

Has this output with GCC:

1e-08

and the following output with MSVC

1e-008

How can I make both outputs the same?

I'm sorry if this is a dumb question but I have not found an answer so far. All formatting seems to evolve around the formatting of everything before the mantissa...

EDIT: The output of GCC is 1e-08 not 1e-8 (as originally stated) so it is conforming. Sorry for the confusion.

EDIT2: Actually renamed "mantissa" to "exponent" following Dietmar's remark. There also is a section on Wikipedia on mantissa vs. significant.


回答1:


There is no manipulator controlling the formatting of the exponent (I assume you mean the exponent rather than the mantissa; also, the "official" name used for the mantissa is significant). To make matters worse, I can't see any rule in the C standard which restricts the formatting of the exponent. I realize that this is about C++ but for the purpose of the formatting details, the C++ standard refers to the C standard.

The only approach I'm aware of is to use an own std::num_put<char> facet which formats the values as desired. This facet would then be put into a std::locale which in turn is imbue()ed into std::cout. A potential implementation could use the default std::num_put<char> facet (or snprintf() which is, unfortunately, probably simpler) to format the floating point number and then strip leading zeros from the exponent.




回答2:


While Dietmar's answer is the clean and probably only really portable answer, I accidentally found a quick-and-dirty answer: MSVC provides the _set_output_format function which you can use to switch to "print exponent as two digits".

The following RAII class can be instantiated in your main() function to give you the same behaviour of GCC, CLANG and 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
    }
};



回答3:


The problem is that Visual C++ was not following the C99 standard. In Visual C++ 2015, _set_output_format was removed since the compiler now follows the standard:

The %e and %E format specifiers format a floating point number as a decimal mantissa and exponent. The %g and %G format specifiers also format numbers in this form in some cases. In previous versions, the CRT would always generate strings with three-digit exponents. For example, printf("%e\n", 1.0) would print 1.000000e+000. This was incorrect: C requires that if the exponent is representable using only one or two digits, then only two digits are to be printed.

In Visual Studio 2005 a global conformance switch was added: _set_output_format. A program could call this function with the argument _TWO_DIGIT_EXPONENT, to enable conforming exponent printing. The default behavior has been changed to the standards-conforming exponent printing mode.

See Breaking Changes in Visual C++ 2015. For older versions, see @Manuel's answer.

FYI, in the C99 standard, we can read:

e,E

A double argument representing a floating-point number is converted in the style [-]d.ddd e(+-)dd, where there is one digit (which is nonzero if the argument is nonzero) before the decimal-point character and the number of digits after it is equal to the precision; if the precision is missing, it is taken as 6; if the precision is zero and the # flag is not specified, no decimal-point character appears. The value is rounded to the appropriate number of digits. The E conversion specifier produces a number with E instead of e introducing the exponent. The exponent always contains at least two digits, and only as many more digits as necessary to represent the exponent. If the value is zero, the exponent is zero. A double argument representing an infinity or NaN is converted in the style of an f or F conversion specifier.

This is a difference compared to C90 that did not give any indication concerning the required exponent length.

Note that the recent Visual C++ changes also concern how to print nan, inf etc.



来源:https://stackoverflow.com/questions/9226400/portable-printing-of-exponent-of-a-double-to-c-iostreams

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!