Как обращаться со структурой с двумя неподписанными шортами, как если бы это был неподписанный int? (in C)
Я создал структуру для представления положительного числа с фиксированной точкой. Я хочу, чтобы числа в обеих сторонах десятичной точки состояли из 2 байт.
typedef struct Fixed_t {
unsigned short floor; //left side of the decimal point
unsigned short fraction; //right side of the decimal point
} Fixed;
Теперь я хочу добавить два числа с фиксированной точкой, Fixed x и Fixed y. Для этого я рассматриваю их как целые числа и складываю.
(Fixed) ( (int)x + (int)y );
, но так как моя визуальная студия 2010 компилятор говорит, Я не могу конвертировать между Fixed и int.
Как правильно это сделать?
EDIT: я не привержен реализации {short floor, short fraction} Fixed.
9 ответов:
Немного магии:
typedef union Fixed { uint16_t w[2]; uint32_t d; } Fixed; #define Floor w[((Fixed){1}).d==1] #define Fraction w[((Fixed){1}).d!=1]Ключевые моменты:
- я использую целочисленные типы фиксированного размера, поэтому вы не зависите от того, что
shortявляется 16-разрядным иint32-разрядным.- макросы для
FloorиFraction(заглавными буквами, чтобы избежать столкновения с функциейfloor()) обращаются к двум частям независимо от конца, какfoo.Floorиfoo.Fraction.Edit: по просьбе OP, объяснение макросов:
Союзы-это способ объявления объекта, состоящего из нескольких различные перекрывающиеся типы. Здесь мы имеем
uint16_t w[2];перекрытиеuint32_t d;, что позволяет получить доступ к значению в виде 2 16-битных единиц или 1 32-битной единицы.
(Fixed){1}является составным литералом и может быть записан более подробно как(Fixed){{1,0}}. Его первый элемент (uint16_t w[2];) инициализируется с помощью{1,0}. Выражение((Fixed){1}).dзатем вычисляется до 32-разрядного целого числа, первая 16-разрядная половина которого равна 1, а вторая 16-разрядная Половина равна 0. В системе Литтл-Энди это значение равно 1, поэтому((Fixed){1}).d==1принимает значение 1 (true) и((Fixed){1}).d!=1принимает значение 0 (false). В большой эндианской системе все будет наоборот.Таким образом, на малоэндовой системе
В теории гипотетическая система может использовать совершенно другое представление для 16-битных и 32-битных значений (например чередование битов двух половин), ломая эти макросы. На практике этого не произойдет. :- )Floorявляетсяw[1]иFractionявляетсяw[0]. В системе большого концаFloorявляетсяw[0]иFractionявляетсяw[1]. В любом случае, вы в конечном итоге сохраняете/получаете доступ к правильной половине 32-битного значения для конечной версии вашей платформы.
Вы можете попытаться сделать неприятный хак, но здесь есть проблема с эндианнесом. Что бы вы ни делали для преобразования, как компилятор должен знать, что вы хотите, чтобы
floorбыла наиболее значимой частью результата, аfraction- менее значимой частью? Любое решение, основанное на переинтерпретации памяти, будет работать для одного конца, но не для другого.Вы должны либо:
(1) Определите преобразование явно. Предполагая, что
shortравно 16 битам:unsigned int val = (x.floor << 16) + x.fraction;(2) Изменение
Если вы хотите, чтобы сложение было быстрым, то (2) - это то, что нужно сделать. Если у вас 64-битный тип, то вы также можете выполнять умножение без разложения:Fixedтаким образом, он имеет членintвместо двух шорт, а затем разлагает, когда требуется, а не , составляя, когда требуется.unsigned int result = (((uint64_t)x) * y) >> 16.Мерзкий Хак, кстати, был бы такой:
unsigned int val; assert(sizeof(Fixed) == sizeof(unsigned int)) // could be a static test assert(2 * sizeof(unsigned short) == sizeof(unsigned int)) // could be a static test memcpy(&val, &x, sizeof(unsigned int));Это будет работать в системе big-endian, где Fixed не имеет заполнения (а целочисленные типы не имеют бит заполнения). В системе Литтл-Энди члены Fixed должны быть в другом порядке, вот почему это неприятно. Иногда кастинг через memcpy-это правильная вещь (в этом случае это "трюк", а не"неприятный Хак"). Просто сейчас не тот случай.
Если вам нужно, вы можете использовать союз, но остерегайтесь эндианских проблем. Вы можете обнаружить, что арифметика не работает и, конечно, не является портативной.
typedef struct Fixed_t { union { struct { unsigned short floor; unsigned short fraction }; unsigned int whole; }; } Fixed;Что более вероятно (я думаю), чтобы работать big-endian (который Windows/Intel не является).
Это невозможно переносить, так как компилятор не гарантирует, что
Fixedбудет использовать тот же объем пространства, что иint. Правильный путь-определить функциюFixed add(Fixed a, Fixed b).
Просто добавьте кусочки отдельно. Вам нужно знать значение дроби, которая означает " 1 " - здесь я называю это
FRAC_MAX:// c = a + b void fixed_add( Fixed* a, Fixed* b, Fixed* c){ unsigned short carry = 0; if((int)(a->floor) + (int)(b->floor) > FRAC_MAX){ carry = 1; c->fraction = a->floor + b->floor - FRAC_MAX; } c->floor = a->floor + b->floor + carry; }В качестве альтернативы, если вы просто устанавливаете фиксированную точку как находящуюся на границе 2 байта, вы можете сделать что-то вроде:
void fixed_add( Fixed* a, Fixed *b, Fixed *c){ int ia = a->floor << 16 + a->fraction; int ib = b->floor << 16 + b->fraction; int ic = ia + ib; c->floor = ic >> 16; c->fraction = ic - c->floor; }
Попробуйте это:
typedef union { struct Fixed_t { unsigned short floor; //left side of the decimal point unsigned short fraction; //right side of the decimal point } Fixed; int Fixed_int; }
Если ваш компилятор помещает два коротких по 4 байта, то вы можете использовать memcpy для копирования вашего int в вашу структуру, но, как сказано в другом ответе, это не переносится... и довольно уродливый.
Вы действительно хотите добавить отдельно каждое поле в отдельном методе? Вы хотите сохранить целое число из соображений производительности?