Здравствуйте, cppguard, Вы писали:
C>Здравствуйте, fk0, Вы писали:
fk0>>Здравствуйте, cppguard, Вы писали:
fk0>> Сдвигая единицу влево на сколько угодно, она так и остаётся интом. fk0>>Если инт 32-битный, то сдвигать на 62 влево бессмысленно -- UB. fk0>>А сдвиг инта на 63 тоже UB ибо знаковый... C>Ок, я понял идею. Для целевой платформы изпользовать что-то кроме uint8_t не имеет смысла, потому что аппаратное умножение фиксированной точки есть только для однобайтных чисел.
Но компилятор же небось инлайнит как (a*(2^8) + b) * (c*(2^8) + d) = a*c*(2^16) + (b*c + a*d)*(2^8) + b*d
(4 умножения, два сдвига и три сложения). Или по крайней мере вызывает встроенную функцию.
Последнее кстати плохо (т.к. регистровый аллокатор уже не понимает какие регистры испортятся и вынужден
что попало заталкивать в стек).
А какой смысл в "фиксированной точке" и 8-битных числах??? Любые практические данные
которые потребуется обрабатывать едва ли влезут в 16 бит. Хорошо бы иметь 24. А 32 может быть даже много.
Звук например. Сам-то прекрасно в 12 бит влезает, но какая-то обработка в два раза больше потребует.
Например, цифровой фильтр который выделяет узкую полосу частот. Помню сигнал с акселерометра обсчитывал.
Там и 8 бит на входе более чем, но нужно же считать векторные произведения, потом тригонометрические функции,
в итоге 16 не хватает для получения приемлемой точности. Вот 24 в самый раз.
fk0>> Интерфейсы должны быть очевидные и по-возможности не допустать неправильного использования. fk0>>В том числе неявным образом. Потому, что шаблон могут передать в другой шаблон и там инстанцировать fk0>>с третьим шаблоном и всё это может быть сделано из макроса. И на каждом этапе вроде очевидно, а потом fk0>>нашла коса на камень. И гораздо лучше если оно не компилируется сразу, чем потом глючит. C>Ну, не работает это в С++. Я могу с полпинка сломать STL, передав неправильный параметр шаблону. Инструменты нужно использовать с умом, а не бить молотком по пальцу и ругаться, что больно. Из-за вот этой вот нездоровой гонки за мнимой типобезопасностью в С++ язык стал такой уровдливый и неповоротливий — UB на каждый чих.
Просто нужно нормально писать код. Что на C, что на C++. Со временем пишешь сам так, что компилятор
варнингов даже не даёт. Обычный набор варнингов для GCC/Clang: -Wall -Wextra -Wcast-align -fstrict-aliasing,
ещё -Wconversion, но не все, от проекта зависит. И полезно прогонять с -fsanitize=undefined.
fk0>> Любое волшебное число в коде, кроме 1, 0, 1000, 1000000 и т.п. -- источник ошибок. fk0>>И на код ревью сразу нужно спрашивать, почему именно 5, а не 42. Что это значит. fk0>>И если это что-то значит, то может быть стоит завести именнованную константу с этим числом. C>Это просто тупо и попахивает слепым следованием "священным заветам". Pi — тоже константа, предложишь и её сделать какой-то именованой?
Нет, это попахивает полезными практиками промышленного программирования.
Потому, что когда ты будешь вбивать число пи руками в код, то можешь на одну циферку
где-то недалеко от точки ошибиться. И никто не заметит даже. А потом Луна-25 улетает
на полной скорости в Луну. И ошмётков не соберёшь. Потому, что орбиту с неправильным пи
посчитало тоже неправильно.
Число пи, кстати, в стандартной C/C++-библиотеке существует как константа M_PI.
И в C++20 там ещё массу всяких констант добавили.
C> Типа не Pi, а HalfAroundCircleInUnitsThatCorrespondToDegreesWithMultiplier57? C> В данном случае 5 это константа, которая выводится после оптимизации алгоритма вывода числа. Как её ещё можно назвать кроме как "множитель"?
Мне как потенциальному код-ревьюверу, например, совершенно непонятно, почему 5, а не 6 или не 4.
fk0>> Это не принципиально, если только где-то не распечатываются гигабайты строк в секунду. C>Это принципиально, если на каждый цикл нужно выводить число на экран. Речь про embedded, тут нельзя написать тысячу строк, загрузив ЦП на пару секунд, и сказать, что наш super-senior software donnut eater так видит.
Нет. Каждый раз когда хотят выводить в цикле число на экран делают какую-то ерунду.
Потому, что экран никто не будет читать 100500 раз в секунду. Это даже монитор не покажет,
он обновляется только 60 раз в секунду. И если речь именно об экране, то чаще чем 60 раз в секунду
(а практически, чаще чем 5 раз в секунду -- человек не прочитает) обновлять информацию нет смысла.
И если цикл короче сотни миллисекунд, то нет никакого смысла выводить в каждом цикле. Есть смысл
в очередном цикле подумать а нужно ли выводить, и выводить только если времени с предыдущего вывода
прошло более 100мс, например. Или вовсе выводить информацию независимо, по таймеру, просто тупо 5
раз в секунду. Независимо от циклов. Из переменной в памяти, в которую можно складывать хоть 100500
раз в секунду. Разумеется не строки, а ещё числа.
fk0>> И кстати, плавающая точка может оказаться БЫСТРЕЙ фиксированной точки. И часто оказывается. fk0>>Могу рассказать историю, как я заморачивался с вычислениями с фиксированной точкой на микроконтроллере. fk0>>С теми же соображениями. Но в 16 бит не влезает ничего толком, значит надо 32. И потом оказалось, что fk0>>32-битное целочисленное решение тот же алгоритм считает МЕДЛЕНЕЙ, чем с программной реализацией операций fk0>>с плавающей точкой. Как так? Да просто: в плавающей точке 24 бита считать надо, а не 32. А контроллер fk0>>вообще 8-битный, он 32 бит не за одну, а за 4-8 операций сложит только. С перемножением ещё хуже: там fk0>>32 бита перемножить сильно сложней, чем 24 бита мантиссы. А порядок же тупо складывается при перемножении, fk0>>быстро, одной инструкцией. Зато нормализация при сложении медленей. Но в целом -- обогнало. fk0>>На 32-битном контроллере конечно целочисленные вычисления быстрей, но тоже могут быть нюансы: если есть fk0>>FPU, то вычисления с плавающей точкой могут оказаться быстрей просто потому, что блок FPU работает fk0>>параллельно с целочисленным АЛУ. И компилятор распараллелит инструкции так, что в целом всё быстрей fk0>>посчитается, чем только на одном целочисленном АЛУ. C>Всё попахивает непониманием матчасти. Fixed-point это обычные целочисленные вычисления с одним нюансом: при умножении нужно делать сдвиг. В моём случае у AVR есть инструкция, которая делает умножение и сдвиг за то же время, что и обычное умножение. Так что ни при каких условиях (кроме ASIC и/или наличия супер-скалярной архитектуры) не может fixed-point быть медленнее floating-point.
См. выше и читай ещё раз. Алгоритмы с плавающей точкой используют мантиссу меньшего размера, чем
алгоритмы с целыми числами. Потому, что у них есть байт порядка. А целочисленные алгоритмы вынуждены
использовать больше разрядов, иначе им просто большие числа не представить. И мантисса меньшего размера
перемножаться может быстрей, чем более длинные целочисленные числа. Что тут непонятного? Конечно это
справедливо для 8/16-битных процессоров с медленным умножением. А на 64-битном всё будет наоборот.
Сдвиг при умножении это всего лишь нормализация. Которая на самом деле не имеет смысла на каждой операции
в алгоритме: совершенно не важно, что в середине вычислений в промежуточном результате единица предствляется
именно единицей, а не как 65536. Можно всё посчитать и в конце нормализовать. Или только на отдельных
этапах вычислений (чтоб умещалось в разрядность). На каждом шаге это делать -- просто дурость. Я потому и говорю
нет смысла говорить о "фиксированной точке", как о чём-то особенном, проще сразу считать в целых числах.
Помимо прочего, из-за нормализации промежуточных результатов запросто получится потеря точности потому, что
промежуточные результаты могут быть плохо представимы с фиксированной точкой, там может очень большие или очень
маленькие значения выходить будут.