С++ с плавающей запятой в строку

Есть ли в С++ ЛЮБОЙ механизм (функция или что-то еще) для преобразования числа с плавающей запятой (или двойного числа) в представление, которое поддерживает как точность числа, так и минимальную длину числа? Я имею в виду что-то вроде JavaScript.

Похоже, C++ не хочет этого делать, например:

  • std::to_string(1.23456789e10); // возвращает "12345678900.000000" (лишние нули)
  • (std::ostringstream() << 1.23456789e10).str() // возвращает "1.234567e10" (две цифры потеряны)
  • snprintf(буфер, sizeof(буфер), "%g", 1.23456789e10); // как указано выше
#include <iomanip>
PaulMcKenzie 13.04.2023 17:45

Что не так с setprecision()?

πάντα ῥεῖ 13.04.2023 17:46

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

Some programmer dude 13.04.2023 17:48
"%g" -- Даже при использовании snprintf спецификатор формата имеет параметры для управления отображением числа. Вы, должно быть, наткнулись на это, так как знали, как использовать %g.
PaulMcKenzie 13.04.2023 17:48

И какова реальная и основная проблема, которую вы пытаетесь решить? Почему строки, которые вы получаете, не решают эту проблему?

Some programmer dude 13.04.2023 17:49

Если вы можете использовать C++20, забудьте о параметрах printf и stream: используйте en.cppreference.com/w/cpp/utility/format/formatter

Pepijn Kramer 13.04.2023 17:49

Нет. Здесь есть некоторая история. C и C++ предшествовали тому времени, когда существовали эффективные алгоритмы для точного и минимального преобразования. Поэтому некоторые реализации стандартных библиотек не всегда конвертируются оптимальным образом. Разработчики компиляторов явно не желают вносить обратно несовместимые изменения в этой области, поэтому стандарт C++ никогда не обновлялся, чтобы требовать точных преобразований. Лучше всего использовать библиотеку, которая может выполнять это преобразование, например двойное преобразование от Google.

john 13.04.2023 18:01

Какой-то фон. OP запрашивает точное преобразование с плавающей запятой (поэтому обратное преобразование воспроизводит исходное число) и использует минимальное количество цифр (поэтому просто вывод, скажем, 25 значащих цифр не является решением). Это удивительно сложная задача, а эффективные алгоритмы были открыты сравнительно недавно.

john 13.04.2023 18:08

Спасибо @john, это ответ, который я действительно знаю, но хотел, чтобы какой-нибудь хороший программист подтвердил его.

no one special 13.04.2023 19:02

Библиотека, которая может делать то, что ты хочешь, Рю: github.com/ulfjack/ryu

prapin 13.04.2023 22:18

«с плавающей запятой в строку» и «преобразовать число с плавающей запятой (или двойное число) в представление, которое поддерживает как точность числа, так и разумную длину числа?» --> Да. std::cout << std::hexfloat << fp << '\n' и std::cout << std::scientific << std::setprecision(std::numeric_limits<decltype(fp)>::max_dig‌​its10) << fp << std::endl;

chux - Reinstate Monica 13.04.2023 23:36

коллеги, столько вопросов и столько минусов, а помощи не оказано. Это на самом деле очень полезный вопрос, поэтому, пожалуйста, внимательно прочитайте. *** iomanip - ага, шапку знаю, чем может помочь? *** setprecission, "%g.*" - в обоих случаях вам нужно вручную установить точность, а я бы хотел, чтобы она была автоматической. Тот же результат, что и в JavaScript, как я описал. *** Средство форматирования C++20 — это круто, но в g++ оно еще не реализовано.

no one special 13.04.2023 23:37

*** Во многих случаях std::to_string выдает неверные результаты. *** и да, в C++23 (в будущем) есть std::to_chars — еще один ремейк существующих решений, которые сейчас ничего не решают. И в следующих релизах C++ будет гораздо больше подобного материала, бот ничего, что я мог бы использовать *** Внешняя библиотека? Конечно, почему бы и нет! Повысить даже. Это все-таки не перебор?

no one special 13.04.2023 23:37

@chux-ReinstateMonica, да, но для версии 0.123 будет добавлено столько ненужных нулей...

no one special 13.04.2023 23:42

@noonespecial "добавь так много ненужных нулей" --> Это не часть вопроса. Заявленной целью вопроса было «а также разумная длина числа». Разумный, а не минимальный. Все еще разумно IMO с max_digits10 конечными нулями. В противном случае обратите внимание на sprintf(buf, "%.*g", DBL_DECIMAL_DIG, fp) или его эквивалент C++, если таковой имеется. Почему важно удалять конечные нули?

chux - Reinstate Monica 13.04.2023 23:52

@chux-ReinstateMonica «Разумно, а не минимально». -> достаточно честно. Спасибо за указание.

no one special 13.04.2023 23:54

@chux-ReinstateMonica Я только что проверил, что вы сказали, и на самом деле будет напечатана минимальная длина, если мы используем std::ios_base& defaultfloat( std::ios_base& str ); Это похоже на то, что я искал! Я проверил это, и это действительно работает! Без вашей помощи я бы не стал этим заниматься! Спасибо!

no one special 13.04.2023 23:59
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
17
301
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ответ не так очевиден, но на самом деле да. Здесь я делюсь кодом, который преобразует фундаментальные типы в 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».

Eric Postpischil 14.04.2023 13:00

В другом примере для точно представляемого значения 123,834756380650873097692965529859066009521484375 результат должен быть «123,83475638065087». Но код в этом ответе выдает «123.8347563806509». Это значение ближе к 123,8347563806509015194023959338665008544921875, поэтому точность была потеряна.

Eric Postpischil 14.04.2023 13:05

Привет @EricPostpischil. Это очень хороший показатель! Большое спасибо!

no one special 14.04.2023 13:25

@EricPostpischil Я добавил «важное примечание», относящееся к тому, что вы сказали об устранении проблемы. Большое спасибо!

no one special 14.04.2023 14:49

Другие вопросы по теме