Есть ли в С++ ЛЮБОЙ механизм (функция или что-то еще) для преобразования числа с плавающей запятой (или двойного числа) в представление, которое поддерживает как точность числа, так и минимальную длину числа? Я имею в виду что-то вроде JavaScript.
Похоже, C++ не хочет этого делать, например:
Что не так с setprecision()?
Каков ожидаемый результат для этого ввода (имея в виду, что фактическое число может быть не совсем точно представлено типом с плавающей запятой)?
"%g" -- Даже при использовании snprintf спецификатор формата имеет параметры для управления отображением числа. Вы, должно быть, наткнулись на это, так как знали, как использовать %g.
И какова реальная и основная проблема, которую вы пытаетесь решить? Почему строки, которые вы получаете, не решают эту проблему?
Если вы можете использовать C++20, забудьте о параметрах printf и stream: используйте en.cppreference.com/w/cpp/utility/format/formatter
Нет. Здесь есть некоторая история. C и C++ предшествовали тому времени, когда существовали эффективные алгоритмы для точного и минимального преобразования. Поэтому некоторые реализации стандартных библиотек не всегда конвертируются оптимальным образом. Разработчики компиляторов явно не желают вносить обратно несовместимые изменения в этой области, поэтому стандарт C++ никогда не обновлялся, чтобы требовать точных преобразований. Лучше всего использовать библиотеку, которая может выполнять это преобразование, например двойное преобразование от Google.
Какой-то фон. OP запрашивает точное преобразование с плавающей запятой (поэтому обратное преобразование воспроизводит исходное число) и использует минимальное количество цифр (поэтому просто вывод, скажем, 25 значащих цифр не является решением). Это удивительно сложная задача, а эффективные алгоритмы были открыты сравнительно недавно.
Спасибо @john, это ответ, который я действительно знаю, но хотел, чтобы какой-нибудь хороший программист подтвердил его.
Библиотека, которая может делать то, что ты хочешь, Рю: github.com/ulfjack/ryu
«с плавающей запятой в строку» и «преобразовать число с плавающей запятой (или двойное число) в представление, которое поддерживает как точность числа, так и разумную длину числа?» --> Да. std::cout << std::hexfloat << fp << '\n' и std::cout << std::scientific << std::setprecision(std::numeric_limits<decltype(fp)>::max_digits10) << fp << std::endl;
коллеги, столько вопросов и столько минусов, а помощи не оказано. Это на самом деле очень полезный вопрос, поэтому, пожалуйста, внимательно прочитайте. *** iomanip - ага, шапку знаю, чем может помочь? *** setprecission, "%g.*" - в обоих случаях вам нужно вручную установить точность, а я бы хотел, чтобы она была автоматической. Тот же результат, что и в JavaScript, как я описал. *** Средство форматирования C++20 — это круто, но в g++ оно еще не реализовано.
*** Во многих случаях std::to_string выдает неверные результаты. *** и да, в C++23 (в будущем) есть std::to_chars — еще один ремейк существующих решений, которые сейчас ничего не решают. И в следующих релизах C++ будет гораздо больше подобного материала, бот ничего, что я мог бы использовать *** Внешняя библиотека? Конечно, почему бы и нет! Повысить даже. Это все-таки не перебор?
@chux-ReinstateMonica, да, но для версии 0.123 будет добавлено столько ненужных нулей...
@noonespecial "добавь так много ненужных нулей" --> Это не часть вопроса. Заявленной целью вопроса было «а также разумная длина числа». Разумный, а не минимальный. Все еще разумно IMO с max_digits10 конечными нулями. В противном случае обратите внимание на sprintf(buf, "%.*g", DBL_DECIMAL_DIG, fp) или его эквивалент C++, если таковой имеется. Почему важно удалять конечные нули?
@chux-ReinstateMonica «Разумно, а не минимально». -> достаточно честно. Спасибо за указание.
@chux-ReinstateMonica Я только что проверил, что вы сказали, и на самом деле будет напечатана минимальная длина, если мы используем std::ios_base& defaultfloat( std::ios_base& str ); Это похоже на то, что я искал! Я проверил это, и это действительно работает! Без вашей помощи я бы не стал этим заниматься! Спасибо!





Ответ не так очевиден, но на самом деле да. Здесь я делюсь кодом, который преобразует фундаментальные типы в std::string, сохраняя точность и относительно короткое представление типов float и double.
template <typename Type>
constexpr static std::string toString(Type value) noexcept
{
std::ostringstream oss;
if constexpr (std::is_floating_point<typename std::remove_cv<Type>::type>::value)
oss << std::defaultfloat << std::setprecision(std::numeric_limits<Type>::digits10 + 1);
oss << value;
return oss.str();
}
Теперь немного предыстории. Приведенный выше код основан на std::ostringstream. Вы можете подумать, почему приведенная выше реализация лучше, чем, например, std::to_string? Более того, почему бы не использовать другие методы, поскольку C++ предоставляет множество различных методов, предлагающих похожие вещи. Хорошо, тогда давайте пройдемся по каждому методу:
std::string: Во-первых, это может привести к недопустимым результатам. Особенно для малых значений float и doubleисточника. Кроме того, он добавляет много ненужных нулей, например, для ввода 1.23456789e10 функция выдает «12345678900.000000».snprintf со спецификатором "%g". Он производит либо "%f", либо "%e", что короче. К сожалению, снижает точность, хотя ее можно сохранить. Например, 1.23456789e10 дает «1.234567e10». Кроме того, если вы укажете точность вручную, она либо добавит ненужные нули, либо удалит менее значащие цифры. Мы хотели бы иметь что-то, что автоматически поддерживает точность и короткую длину строки.std::format: О боже, я годами ждал эту функцию. К сожалению, на сегодняшний день только MSVC и clang реализуют заголовок, а в g++ он по-прежнему отсутствует.std::to_chars: Функция C++23, еще не существующая. Похоже на очередной ремейк std::to_string.std::ostringstream сам по себе не сохраняет точность числа. Например, 1.23456789e10 дает «1.234567e10». Требуется указать std::setprecision вместе с std::defaultfloat, чтобы сохранить точность и относительно короткое (удобное для пользователя) представление. Для получения дополнительной информации, пожалуйста, обратитесь к cppreference.com . И в целом это не так очевидно - для получения дополнительной информации обратитесь к источнику.
Важное примечание: код не совсем поддерживает точность, как указано выше. Это было сделано намеренно. Если вы посмотрите внимательно, std::numeric_limits<Type>::digits10 — это «количество десятичных цифр, которые могут быть представлены без изменений», а std::numeric_limits<Type>::max_digits10 — это «количество десятичных цифр, необходимое для различения всех значений этого типа». Поэтому вам нужно самостоятельно решить, что именно вам нужно. Если вы представляете число пользователю, digits10 является наиболее «дружественным» представлением (0,3f равно «0,3» для числа с плавающей запятой), однако только max_digits10 даст вам точное представление (0,3f равно «0,300000012» для числа с плавающей запятой). . Спасибо, Эрик, за указание на это!
Это не дает желаемых результатов. Для .1+.2 результат должен быть «0,300000000000000004», потому что из-за округления double результат .1+.2 немного выше double, ближайшего к .3. Например, JavaScript «console.info(.1+.2)» выдает «0,300000000000000004». Однако код в этом ответе выдает «0,3».
В другом примере для точно представляемого значения 123,834756380650873097692965529859066009521484375 результат должен быть «123,83475638065087». Но код в этом ответе выдает «123.8347563806509». Это значение ближе к 123,8347563806509015194023959338665008544921875, поэтому точность была потеряна.
Привет @EricPostpischil. Это очень хороший показатель! Большое спасибо!
@EricPostpischil Я добавил «важное примечание», относящееся к тому, что вы сказали об устранении проблемы. Большое спасибо!