Обозначение Q — это способ указать параметры двоичного формата чисел с фиксированной запятой . Например, в нотации Q формат чисел обозначается как Q8.8
означает, что числа с фиксированной запятой в этом формате имеют 8 бит для целой части и 8 бит для дробной части.
С той же целью использовался ряд других обозначений .
Обозначение Q, как определено Texas Instruments , [1] состоит из буквы Q , за которой следует пара чисел m . n , где m — количество битов, используемых для целой части значения, а n — количество дробных битов.
По умолчанию эта запись описывает знаковый двоичный формат с фиксированной запятой, при этом немасштабированное целое число хранится в формате дополнения до двух , используемом в большинстве двоичных процессоров. Первый бит всегда указывает знак значения (1 = отрицательный, 0 = неотрицательный) и не учитывается в параметре m . Таким образом, общее количество используемых битов w равно 1 + m + n .
Например, спецификация Q3.12 описывает знаковое двоичное число с фиксированной запятой с общим количеством w = 16 бит, включая знаковый бит, три бита для целой части и 12 бит, которые являются дробной частью. То есть 16-битное целое число со знаком (дополнение до двух), которое неявно умножается на масштабный коэффициент 2–12 .
В частности, когда n равно нулю, числа являются просто целыми числами. Если m равно нулю, все биты, кроме знакового бита, являются дробными битами; тогда диапазон сохраненного числа составляет от -1,0 (включительно) до +1,0 (исключительно).
М и точку можно опустить, и в этом случае они определяются на основе размера переменной или регистра, в котором хранится значение . Таким образом, Q12 означает целое число со знаком с любым количеством бит, которое неявно умножается на 2-12 .
Буква U может быть добавлена к Q для обозначения беззнакового двоичного формата с фиксированной запятой. Например, UQ1.15 описывает значения, представленные как 16-битные целые числа без знака с неявным коэффициентом масштабирования 2 −15 , который находится в диапазоне от 0,0 до (2 16 −1)/2 15 = +1,999969482421875.
Вариант обозначения Q использовался ARM . В этом варианте число m включает знаковый бит. Например, 16-битное целое число со знаком будет обозначаться Q15.0
в варианте TI, а Q16.0
в варианте ARM. [2] [3]
Разрешение (разница между последовательными значениями) Q m . n или UQ м . Формат n всегда равен 2 − n . Диапазон представимых значений зависит от используемых обозначений:
Например, номер формата Q15.1 требует 15+1 = 16 бит, имеет разрешение 2-1 = 0,5, а представимые значения варьируются от -2 14 = -16384,0 до +2 14 - 2 -1 = +16383,5. В шестнадцатеричном формате отрицательные значения варьируются от 0x8000 до 0xFFFF, за которыми следуют неотрицательные значения от 0x0000 до 0x7FFF.
Числа Q представляют собой отношение двух целых чисел: числитель хранится в памяти, знаменатель равен 2 n .
Рассмотрим следующий пример:
Если необходимо сохранить базу числа Q ( n остается постоянным), математические операции с числом Q должны сохранять знаменатель постоянным. Следующие формулы показывают математические операции над общими числами Q и . (Если мы рассмотрим пример, упомянутый выше, это 384 и 256.)
Поскольку знаменатель представляет собой степень двойки, умножение можно реализовать как арифметический сдвиг влево, а деление — как арифметический сдвиг вправо; на многих процессорах сдвиги выполняются быстрее, чем умножение и деление.
Для обеспечения точности промежуточные результаты умножения и деления должны иметь двойную точность, и необходимо внимательно округлять промежуточный результат перед преобразованием обратно в желаемое число Q.
При использовании C выполняются следующие операции (обратите внимание, что здесь Q относится к количеству битов дробной части):
int16_t q_add ( int16_t a , int16_t b ) { return a + b ; }
С насыщенностью
int16_t q_add_sat ( int16_t a , int16_t b ) { int16_t результат ; int32_t тмп ; tmp = ( int32_t ) а + ( int32_t ) б ; если ( tmp > 0x7FFF ) tmp = 0x7FFF ; если ( tmp < -1 * 0x8000 ) tmp = -1 * 0x8000 ; результат = ( int16_t ) tmp ; вернуть результат ; }
В отличие от чисел с плавающей запятой ±Inf, насыщенные результаты не являются липкими и станут ненасыщенными при добавлении отрицательного значения к положительному насыщенному значению (0x7FFF) и наоборот в показанной реализации. В ассемблере флаг Signed Overflow можно использовать, чтобы избежать приведения типов, необходимых для этой реализации C.
int16_t q_sub ( int16_t a , int16_t b ) { return a - b ; }
// предварительно вычисленное значение: #define K (1 << (Q - 1)) // насыщаем до диапазона int16_t int16_t sat16 ( int32_t x ) { if ( x > 0x7FFF ) return 0x7FFF ; иначе , если ( x < -0x8000 ) вернуть -0x8000 ; иначе возврат ( int16_t ) x ; } int16_t q_mul ( int16_t a , int16_t b ) { int16_t результат ; int32_t температура ; temp = ( int32_t ) a * ( int32_t ) b ; // тип результата соответствует типу операнда // Округление; средние значения округляются в большую сторону temp += K ; // Корректируем путем деления на основание и насыщения результата result = sat16 ( temp >> Q ); вернуть результат ; }
int16_t q_div ( int16_t a , int16_t b ) { /* предварительное умножение по основанию (увеличение масштаба до Q16, чтобы результат был в формате Q8) */ int32_t temp = ( int32_t ) a << Q ; /* Округление: средние значения округляются вверх (вниз для отрицательных значений). */ /* ИЛИ сравнить старшие биты, т.е. if (((temp >> 31) & 1) == ((b >> 15) & 1)) */ if (( temp >= 0 && b >= 0 ) || ( temp < 0 && b < 0 )) { temp += b / 2 ; /* ИЛИ сдвиг на 1 бит, т.е. temp += (b >> 1); */ } else { temp -= b / 2 ; /* ИЛИ сдвиг на 1 бит, т.е. temp -= (b >> 1); */ } return ( int16_t )( temp / b ); }