Как стать автором
Обновить

Как напечатать float

Время на прочтение15 мин
Количество просмотров18K
Всего голосов 45: ↑44 и ↓1+43
Комментарии23

Комментарии 23

Однако современный Perl за лето сильно изменился. А по теме - так откуда у вас вообще возникла задача "печатать float"?

Однако современный Perl за лето сильно изменился. 

Конечно. Так и лет прошло немало!

так откуда у вас вообще возникла задача "печатать float"?

Две основных мотивации были:

  1. Улучшить читаемость, заодно улучшить roundtrip. Накипело! Достали мусорные 0.000000 с одной стороны и это при некорректно roundtrip-ающих 123.456 с другой.

  2. Ускорить внутренние большие выборки. Типично это какие-нибудь выгрузки сигналов ранжирования, или "просто" выгрузки документов.

Конечно же, в обслуживающих конечных пользователей OLTP запросах мы распечаткой в таких масштабах, чтобы printf стал заметен по производительности, не занимаемся.

А вот во внутренних выгрузках всякого для ML моделей эффект налицо. Как симпатичный пример, у нас в одном из мест форматированием занимается даже не сам Sphinx, а всунутая в него UDF. Скопировали и туда эту нашу реализацию, и некоторые нужные нам запросы ускорились кратно, вроде бы в диапазоне 2x-3x (точные цифры подзабыл).

Резюмируя, изначально хотелось повысить "качество" распечатки, приоритетом было именно это. Однако заодно удалось вот и подразогнать :)

Конечно. Так и лет прошло немало!

но не настолько же :)
Тоже не так давно допиливал ryu для своих целей. О библиотеке у меня сложилось впечатление: отличная и эффективная внутренняя реализация и ужасный интерфейс (по крайней мере для моих целей). В результате переделал следующим образом ryu -> двоично десятичное представление -> строка. Преобразование в двоично десятичное представление очень эффективно выполняется ryu, а из него уже сделать любое форматирование строки очень просто.

Да, детали public API я подзабыл, но сходные ощущения. Плюс есть еще более общий момент, что в погоне за компактностью и Ryu и другие библиотеки плюют на читаемость совсем. 123000? Ни в коем случае, даешь 123e3, ведь это на 1 байт короче. Такое...

В MySQL была, кстати, и такая задача. Вот, юзер дал нам буфер на N символов. Надо как можно точнее напечатать double в этот буфер. Что-то потеряется, но лучше бы поменьше. Тоже нестандартное требование, патчили dtoa, как я помню.

кмк, все вышеупомянутые проблемы исключительно из-за того, что float применяется не по назначению

Подавляющая масса наших float это вычисленные движком сигналы ранжирования на выходе движка во-первых, и некоторые вектора фичей для ML моделей (те, которые нельзя квантизовать получше) в исходных данных во-вторых. Две колонки {latitude, longitude}, разумеется, не волнуют вообще никого.

Пажите, но ведь именно в latitude and logitude у вас вылезет потеря точности на float представлениях, если там шесть десятичных знаков у градусов.

Интерсно, в реальной жизне бывает больще чем четыри значeших знака в float значениях?

Наглядный пример - геокоординаты.

Координаты это как раз случай, когда лучше применить int с фиксированной точкой. Ибо 4 знака после запятой это уже точность около 8м в худшем случае, 5 знаков это уже 80см. Если нужна большая точность, просто добавляем еще знак. То есть, по сути, это достаточно будет хранить целое число от -180000000 до 180000000 для точности 6 знаков (8см разрешение), для этого хватит int32.

Если сделать int64, точность вообще будет даже слишком большая.

Все так. Использовать на координатах float -- терять около метра точности. Или double, или в интах. Но никак не float.

Совершенно верно. За исключением геодезических измерений или применения в картографии, на практике достаточно 4-5 цифр после запятой. Всегда веселит, когда в различных популярных обзорах в нетехнической прессе координаты какой-либо достопримечательности дают с 7-8 цифрами после запятой или с долями секунды, не понимая, что дают бессмысленную точность на плюс-минус размер спичечного коробка, нужную только геодезистам.

Мда, удовлетворение любопытства и написание велосипедов за счет работодателя может конечно разнообразить рутину и дать возможность выступить на конференции.

Но если действительно требуется сохранять точность, то можно просто почитать документацию и отформатировать float с флагом "a" - "converts floating-point number to the hexadecimal exponent notation."

Да, в результате будет получаться {"value": 0x1.edd2f20000000p+6} вместо {"value":123.456}. К несчастью, пока что все широко используемые в индустрии парсеры JSON с такими документами не справляются совсем. Кроме того, мне жаль глаза наших аналитиков и программистов. Я уверен, они постепенно справятся, но и им будет тяжело, и меня, возможно, жестоко изобьют.

А как ваш парсер JSON относится к Infinity или NaN значениям? Они ведь тоже не часть спецификации.

Желание усидеть на двух стульях понятно, но с плавающей точкой это невозможно. Либо побитовово точное хранение (a hex float оно и есть), либо человекочитаемое с потерей точности.

Теперь понятно почему в Питоне нужно подключать модуль Decimal для работы с десятичными числами.

По поводу C++ - у нас с C++17 ещё есть std::from_chars (который, наконец, заработал на всех основных компиляторах в 20-м году и для float'ов) который порядком быстрее printf-семейства.

Статья интерестная в академичском плане, отсюда и вопрос в академическом плане.

Если уж полезли так низко, до патчей библиотек, почему для решения конкретных задач не вевести конкретный тип?

На одном из проектов где мы сильно мудрили с протоколами передачи данных мы ввели аж 8 типов для хранения float которые решали каждый, свою узко специализированную задачу?

Не считая типов упаковки для int коих было не меньше.

Можете опубликовать список Ваших тестовых float значений? Раз уж от 1 до 10 недостаточно :)

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий