Как обращаться со структурой с двумя неподписанными шортами, как если бы это был неподписанный 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-разрядным иint
32-разрядным.- макросы для
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 в вашу структуру, но, как сказано в другом ответе, это не переносится... и довольно уродливый.
Вы действительно хотите добавить отдельно каждое поле в отдельном методе? Вы хотите сохранить целое число из соображений производительности?