Re[3]: Fixed-point to string
От: fk0 Россия https://fk0.name
Дата: 29.09.23 09:42
Оценка:
Здравствуйте, cppguard, Вы писали:

C>Здравствуйте, fk0, Вы писали:


fk0>> Вот здесь 1 — это int, а sizeof(value) может быть меньше чем sizeof(intmax_t).

C>И? Каким боком sizeof тут?

Сдвигая единицу влево на сколько угодно, она так и остаётся интом.
Если инт 32-битный, то сдвигать на 62 влево бессмысленно -- UB.
А сдвиг инта на 63 тоже UB ибо знаковый...

fk0>> А что если N задали нулевым? Можно же!

C>Можно, конечно. Ещё можно комп облучить, и тогда некоторые биты в памяти поменяют своё значение.

Можно хотя бы статик ассерт написать. Чтоб не ловить потом UB в продакшене.
Потому, что ноль туда может прийти неочевидным образом.

fk0>> А что если value -- знаковое. Сдвиг вправо может быть с сохранением знака.

fk0>>А может быть без.
C>Может. А ещё value может быть классом, ссылкой и т.д.

Довод за концепты. А то чё там будет с std::ostream вообще жутко представить.
Можно хотя бы enable_if написать и/или static_assert опять же.

fk0>> Такой параметр шаблона по-умолчанию -- источник ошибок.

C>А какой — не источник?

Интерфейсы должны быть очевидные и по-возможности не допустать неправильного использования.
В том числе неявным образом. Потому, что шаблон могут передать в другой шаблон и там инстанцировать
с третьим шаблоном и всё это может быть сделано из макроса. И на каждом этапе вроде очевидно, а потом
нашла коса на камень. И гораздо лучше если оно не компилируется сразу, чем потом глючит.

fk0>> Опять же Position = 0 и UB.

C>Но зачем?

В любом ПО существуют ошибки. Или даже не ошибки, а просто граничные условия не продуманы.

fk0>> Почему пятёрка захардкожена, что она значит?

C>Она означает множитель, который изначально равен 5.

Любое волшебное число в коде, кроме 1, 0, 1000, 1000000 и т.п. -- источник ошибок.
И на код ревью сразу нужно спрашивать, почему именно 5, а не 42. Что это значит.
И если это что-то значит, то может быть стоит завести именнованную константу с этим числом.

fk0>> Может быть переполнение в acc. Вообще непонятно зачем здесь какие-то рекурсивные вычисления,

fk0>>по-моему всё сводится к одной линейной арифметической операции и отправке числа в ostream/snprintf/std::format.
C>Предлагаешь длинную арифметику вводить для acc? ostream/snprintf/std::format — ничего из этого не умеет выводить fixed-point.

Я длинной арифметики тут не увидел. Она спрятана за типом T? Опять же довод за то, что нужны
концепты, enable_if, std::detected, std::is_base_of хотя бы. Чтоб было вообще понятно, что T
это вовсе не любой T, а подмножество таких T обладающих определёнными свойствами. И опять же
чтоб по ошибке не подсунуть чёрти что и долго не удивляться.

fk0>> Вообще для ввода-вывода (где перформанс не критичен) не зазорно сконвертировать в double.

C>Боюсь, что конвертация fixed-pont -> floating-point может выйти ещё тяжелее чем вывод в строку.

Это не принципиально, если только где-то не распечатываются гигабайты строк в секунду.

fk0>> Вообще почему именно "числа с фиксированной точкой", а не рационалные дроби? Кажется не нужна эта

fk0>>фиксированная точка вовсе. Проще тупо считать всегда в целых, домножив всё на некий коэффициент K
fk0>>чтоб значения не утонули в шумах квантизации. А потом при выводе обратно поделить на K. В принципе
fk0>>число с фиксированной точкой это оно и есть, только там K -- обязательно 2^N. Но это же совершенно
fk0>>не обязательно. Хотя и удобно для быстрого деления. Но давно уже не обязательно: современные процессоры
fk0>>уж точно наверняка имеют быстрый умножитель, с помощью которого деление или умножение на константу
fk0>>делается быстро.
C>Смешались в кучу кони-люди. Фиксированная точка удобна там, где вычисления находятся в одном диапазоне, а аппаратной поддержки float/double нет. Рациональные числа вообще никак не решают проблему, потому что переполнение с числителе или знаменетеле так и останется переполнением.

Ничего не смешалось, я прекрасно представляю проблемную область и утверждаю, что фиксированная точка
особенного смысла не имеет. Все вычисления делятся на в общем-то три следующих класса:

1. целочисленные;
2. плавающая точка;
3. целочисленные вычисления с неограниченной разрядностью
(библиотеки bignum и т.п., обычно криптография, бухгалтерия с числами в ASCII);
4. экзотические системы счисления: фиббоначиева, логарифмическая и т.п.

Фиксированная точка -- по сути это первое (или третье). И в ней именно как в "фиксированной точке" нет никакого смысла,
это именно то что я сказал: данные домножены на 2^N и младшие биты это именно то, что после точки. Называть
это специальным термином, как-то обособлять нет смысла. Вообще смысла думать о точке смысла нет. Повторюсь,
что домжножать можно на любое K не являющееся 2^N в общем случае. И о какой-то точке вообще не задумываться.
Вместо точки рациональная дробь (x/K), где K можно вообще вынести за скобки и только на выводе домножить
на 2^N/К и распечатать потом результат поделённый на 2^N, точку, и результат по модулю 2^N — 1. Если вообще
кому-то эта точка нужна. Можно просто оперировать всегда целыми числами домноженными в голове на K и забыть
про точку.

Обычно рациональные числа идут в комплекте с bignum, так что переполнения там нет. Только для практических
задач оно бессмысленно: для криптографии все операции по-модулю, для научно-технических задач плавающая
точка подходит лучше. А в бухгалтерии таких денег никто не видел.

И кстати, плавающая точка может оказаться БЫСТРЕЙ фиксированной точки. И часто оказывается.
Могу рассказать историю, как я заморачивался с вычислениями с фиксированной точкой на микроконтроллере.
С теми же соображениями. Но в 16 бит не влезает ничего толком, значит надо 32. И потом оказалось, что
32-битное целочисленное решение тот же алгоритм считает МЕДЛЕНЕЙ, чем с программной реализацией операций
с плавающей точкой. Как так? Да просто: в плавающей точке 24 бита считать надо, а не 32. А контроллер
вообще 8-битный, он 32 бит не за одну, а за 4-8 операций сложит только. С перемножением ещё хуже: там
32 бита перемножить сильно сложней, чем 24 бита мантиссы. А порядок же тупо складывается при перемножении,
быстро, одной инструкцией. Зато нормализация при сложении медленей. Но в целом -- обогнало.
На 32-битном контроллере конечно целочисленные вычисления быстрей, но тоже могут быть нюансы: если есть
FPU, то вычисления с плавающей точкой могут оказаться быстрей просто потому, что блок FPU работает
параллельно с целочисленным АЛУ. И компилятор распараллелит инструкции так, что в целом всё быстрей
посчитается, чем только на одном целочисленном АЛУ.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.