Comments 26
template <uint8_t FractionLength>
const FixedPoint<FractionLength> operator - (int l, const FixedPoint<FractionLength>& r)
{
return r - FixedPoint<FractionLength>(l);
}
Вычитание так не работает
https://github.com/MikeLankamp/fpm
https://github.com/mizvekov/fp
https://github.com/Pharap/FixedPointsArduino
Не благодарите
Посмотреть уже написанный код, совсем ни то же самое, что написать самому )
Если брать существующие целые типы и просто масштабировать значения, типа хранить значения, умноженные на 10000 или на 2^8 или сколько нужно - то разве нельзя обойтись существующими возможностями компилятора? Объявить только отдельный тип чтобы случайно не сложить масштабированное значение с не масштабированным и всё? Тут, конечно, нужно еще понимать для чего всё это делается.
Фрагменты одного из конструкторов:
explicit FixedPoint(int decimal, unsigned int fraction = 0): val(0)
{
...
int8_t sign = decimal > 0 ? +1 : -1;
...
val |= (decimal << FractionLength);
Из кода очевидно, что decimal
может быть отрицательным.
В последней строке фрагмента кода, процитированного мной, написано:
val |= (decimal << FractionLength);
Built-in bitwise shift operators:
For negative a, the behavior of a << b is undefined.
Соответственно, если конструктор будет вызван с отрицательным decimal
, то случится UB.
Вы правы, проглядел, как я уже сказал тестового покрытия нет
Вы правы, проглядел, как я уже сказал тестового покрытия нет
Тестами такое, как правило, не ловится.
Чтобы это поймать тестами, нужно гонять на платформе, где проявление UB будет отличаться от ожидаемого поведения.
В заголовке должна быть имплементация?
constexpr uint8_t mask(uint8_t num)
{
return (1 << num) - 1;
}
И вообще, рекурсия - это красиво, но для таких примитивных действий (в т.ч. возведение в степень) лучше её не применять. Не забывайте, что это приводит к многократному вызову функции со всеми вытекающими последствиями.
В принципе да, но это constexpr
- надеюсь, что компилятор это развернёт в момент компиляции
Если num всегда будет константой, то это операцию лучше делать дефайном, а ещё лучше по месту вставить ((1<<num)-1). Но наличие функции предполагает, что num
можетбыть переменной. И тогда приплыли.
Библиотека хороша лишь как гимнастика ума, для практических целей применить её не получится. Хотя бы потому, что вам сразу же понадобится умножать числа с разными порядками, а здесь это не предусмотрено. Во-вторых, при таком умножении только пользователь должен решить какой должен быть порядок у результата. Т.е. чем пожертвовать: целой или дробной частью. А значит оператор для этого не годится, даже friend operator* (...). Нужна отдельная функция, в которую придётся передавать не только операнды, но и порядок результата.
Я с вами частично согласен: библиотека игрушечная, но это можно было бы преодолеть несколькими способами (самый очевидный реализовать длинную арифметику, например над 256 разрядными числами и выдать по 128 разрядов на целую и дробную части). В таком дизайне сохранять слишком маленькие дроби никак не получится т.к. цена младшего разряда фиксирована и равна 2^(-FractionLength)
В десктопных приложениях никто такие библиотеки в здравом уме применять не будет, там используют double не задумываясь. Единственное применение подобных библиотек - это микроконтроллеры, но там заводить по 256 бит на каждую переменную - это непозволительная расточительность.
Игрушечная имплементация чисел с фиксированной точкой в C++