Переносная печать экспоненты двойных в С++ iostreams

Я хочу напечатать двойное значение до std::cout portably (GCC, clang, MSVС++), так что вывод будет одинаковым на всех платформах.

У меня проблема с форматированием экспоненты. Следующая программа

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

Имеет этот вывод с GCC:

1e-08

и следующий вывод с MSVC

1e-008

Как я могу сделать оба выхода одинаковыми?

Извините, если это глупый вопрос, но пока я не нашел ответа. Кажется, что все форматирование развивается вокруг форматирования всего до мантиссы...

EDIT: вывод GCC 1e-08 not 1e-8 (как изначально указано), поэтому он соответствует. Извините за путаницу.

EDIT2: Фактически переименовали "мантисса" в "экспоненту" после комментария Дитмара. Также есть раздел о Википедии о мантиссе против значительного.

Ответы

Ответ 1

Нет манипулятора, контролирующего форматирование экспоненты (я предполагаю, что вы имеете в виду экспонента, а не мантисса, а также "официальное" имя, используемое для мантиссы, является значительным). Что еще хуже, я не вижу никакого правила в стандарте C, которое ограничивает форматирование экспоненты. Я понимаю, что речь идет о С++, но для целей форматирования стандарт С++ относится к стандарту C.

Единственный подход, о котором я знаю, - использовать собственный фасет std::num_put<char>, который форматирует значения по желанию. Затем этот грань будет помещен в std::locale, который, в свою очередь, imbue() ed в std::cout. Потенциальная реализация может использовать фасет по умолчанию std::num_put<char> (или snprintf(), который, к сожалению, вероятно, проще) для форматирования числа с плавающей запятой, а затем отделяет ведущие нули от экспоненты.

Ответ 2

В то время как ответ 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
    }
};

Ответ 3

Проблема в том, что Visual С++ не следовала стандарту C99. В Visual С++ 2015, _set_output_format был удален, поскольку компилятор теперь следует стандарту:

Спецификаторы формата %e и %e форматируют число с плавающей запятой в качестве десятичной мантиссы и экспоненты. Спецификаторы формата %g и %g также в некоторых случаях также форматируют числа в этой форме. В предыдущих версиях CRT всегда генерировал строки с трехзначными показателями. Например, printf("%e\n", 1.0) будет печатать 1.000000e+000. Это было некорректно: C требует, чтобы если экспонент был представлен с использованием только одной или двух цифр, тогда должны печататься только две цифры.

В Visual Studio 2005 был добавлен глобальный переключатель соответствия: _set_output_format. Программа может вызвать эту функцию с аргументом _TWO_DIGIT_EXPONENT, чтобы включить соответствующую печать экспонентов. Поведение по умолчанию было изменено на режим печати экспонентов, соответствующий стандарту.

См. Нарушение изменений в Visual С++ 2015. Для более старых версий см. Ответ @Manuel.

FYI, в C99 standard, мы можем прочитать:

е, е

Двойной аргумент, представляющий число с плавающей запятой, преобразуется в стиле [-] d.ddd e (+ -) dd, где есть одна цифра (которая отлична от нуля, если аргумент отличен от нуля) до десятичной точки символ и количество цифр после того, как оно равно точности; если точность отсутствует, она принимается равной 6; если точность равна нулю, а флаг # не указан, символ десятичной точки не отображается. Значение округляется до соответствующего количества цифр. Спецификатор преобразования E производит число с E вместо e, вводящего экспонента. Показатель всегда содержит не менее двух цифр и только столько цифр, сколько необходимо для представления экспоненты. Если значение равно нулю, показатель равен нулю. Двойной аргумент, представляющий бесконечность или NaN, преобразуется в стиле спецификатора преобразования f или F.

Это разница по сравнению с C90, которая не давала никаких указаний относительно требуемой длины экспоненты.

Обратите внимание, что последние изменения Visual С++ также касаются того, как печатать nan, inf и т.д.