Полноценное отображение чисел с плавающей запятой в С++?

Я прочитал несколько тем об отображении чисел с плавающей запятой в C++ и не нашел удовлетворительного ответа.

Мой вопрос: как отобразить все значащие цифры чисел с плавающей запятой в C++ в научном формате (мантисса/экспонента)?

Проблема в том, что все числа имеют разное количество значащих цифр по основанию 10.

Например, double имеет точность от от 15 до 17 значащих десятичных цифр, но std::numeric_limits<double>::digits10 возвращает 15, и, следовательно, для некоторых чисел я потеряю 2 дополнительные десятичные цифры точности.


person Vincent    schedule 26.10.2013    source источник
comment
Итак, используйте precision(std::numeric_limits<double>::digits10 + 2)? Конечно, если последние две цифры являются случайным мусором, сгенерированным в результате ваших вычислений (например, 1.00000000000005 + 100 - 100 [при условии, что компилятор не удалил +100 - 100 как бессмысленные, например, потому что это неизвестные в то время переменные]). Следует отметить, что, например, printf glibc выдаст мусор, если вы напечатаете что-то вроде double value = 1.234E+300 как printf("%f", value); — будет около 280 цифр «случайности»)   -  person Mats Petersson    schedule 26.10.2013
comment
@Mats Petersson Мусор в глазах смотрящего. Если вы напечатаете все n цифр 2^-n, ни одна из них не будет мусором.   -  person Rick Regan    schedule 27.10.2013
comment
@RickRegan: Итак, вы думаете, что 1234000000000001278847239713129823712387918732982173971923... это лучший результат, чем 1234000000000000000000000000000000000000000... ?? [printf, над которым я работаю, делает последнее, но, конечно, когда я использую glibc в качестве эталона, я должен учитывать разницу]. Я понятия не имею, что cout делает в этом случае.   -  person Mats Petersson    schedule 27.10.2013
comment
@MatsPetersson: Как правило, неверно, что «случайный мусор», созданный в результате вычислений, появляется в основном в цифрах после digits10. Это те цифры, которые могут не сохраниться при обратном преобразовании из десятичного числа в число с плавающей запятой, и это только те цифры, на которые влияет наименьшее возможное округление в числах с плавающей запятой. Когда кто-то выполняет более одного вычисления, ошибки могут складываться различными способами, приводя к совокупной ошибке, которая может варьироваться от нуля до бесконечности, в зависимости от обстоятельств.   -  person Eric Postpischil    schedule 27.10.2013
comment
@MatsPetersson: Кроме того, для тех, кто понимает числа с плавающей запятой и осторожно их использует, цифры не лишены смысла. Да, мы используем всю доступную информацию. Среди прочего, количество цифр, которые вам нужно написать, чтобы иметь возможность снова прочитать исходное число, обычно превышает digits10. Поэтому у вас должно быть больше цифр.   -  person Eric Postpischil    schedule 27.10.2013
comment
@ Матс Петерссон Нет, я просто сказал, что если вы знаете, что имеете дело с точно представляемыми значениями - например, со степенью двойки - тогда все цифры что-то значат. В этом случае может пригодиться способность glibc печатать все цифры (см., например, мою статью exploringbinary.com/ ).   -  person Rick Regan    schedule 27.10.2013


Ответы (3)


Значение std::numeric_limits<double>::digits10 обеспечивает количество десятичных разрядов, которые можно безопасно восстановить, т. е. количество десятичных разрядов, которые выживают при циклическом переходе десятичного числа -> double -> десятичного числа. Предполагать, что больше десятичных цифр верны, бесполезно. Если вы хотите упростить круговой путь double->десятичный->double, вы должны использовать std::numeric_limits<double>::max_digits10.

Если вам нужны точные значения, вы должны использовать std::numeric_limits<double>::digits, но он будет отображать числа, преобразованные из десятичных значений забавным образом, поскольку они обычно являются округленными значениями. Это также причина, по которой max_digits10 бесполезен при представлении чисел для восприятия человеком: последние две цифры обычно не те, которые ожидаются человеком-читателем.

person Dietmar Kühl    schedule 26.10.2013

Вы смотрели на std::max_digits10?

Из cppreference:

Значение std::numeric_limits<T>::max_digits10 — это количество десятичных цифр, необходимых для уникального представления всех различных значений типа T, например, необходимых для сериализации/десериализации в текст. Эта константа имеет смысл для всех типов с плавающей запятой.

Смысл этого (и как я его использую) заключается в том, что текстовый вывод можно скопировать/вставить в другую программу, и число будет представлять то же самое число.

Теперь я должен сказать, что мой рабочий формат всегда выравнивается по правому краю %23.16E, и я использую инженерную оценку для последних нескольких цифр. Мне это нравится, потому что этого достаточно для знака, показателя степени и шестнадцати цифр.

-----------------------
-1.1234567812345678E+12

Теперь обратите внимание, что digits точности и decimal digits точности не обязательно одно и то же.

person Escualo    schedule 26.10.2013
comment
+1 за рекомендацию хорошего значения по умолчанию для печати чисел с двойной точностью. Я сам использую % 23.16e, что оставляет место для печати знака, если это необходимо. - person njuffa; 28.10.2013
comment
@njuffa Думаю, у нас одинаковый формат. В моем случае я правильно выравниваю флаги %23.16Evia (в отличие от пробела в объявлении формата), чтобы у меня было место для необязательного знака. Это хороший формат! - person Escualo; 28.10.2013

В C++20 для этого можно использовать std::format. :

std::cout << std::format("{}", M_PI);

Вывод (при условии IEEE754 double):

3.141592653589793

Формат с плавающей запятой по умолчанию — это кратчайшее десятичное представление с гарантией приема-передачи. Преимущество этого метода по сравнению с использованием точности max_digits10 из std::numeric_limits заключается в том, что он не печатает ненужные цифры. Например:

std::cout << std::setprecision(
  std::numeric_limits<double>::max_digits10) << 0.3;

печатает 0.29999999999999999 пока

std::cout << std::format("{}", 0.3);

выводит 0.3 (godbolt).

А пока вы можете использовать библиотеку {fmt}, на которой основан std::format. {fmt} также предоставляет функцию print, которая делает это еще проще и эффективнее (godbolt):

fmt::print("{}", M_PI);

Отказ от ответственности: я автор {fmt} и C++20 std::format.

person vitaut    schedule 17.12.2020