Как обращаться со структурой с двумя неподписанными шортами, как если бы это был неподписанный 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 2

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). В большой эндианской системе все будет наоборот.

Таким образом, на малоэндовой системе Floor является w[1] и Fraction является w[0]. В системе большого конца Floor является w[0] и Fraction является w[1]. В любом случае, вы в конечном итоге сохраняете/получаете доступ к правильной половине 32-битного значения для конечной версии вашей платформы.

В теории гипотетическая система может использовать совершенно другое представление для 16-битных и 32-битных значений (например чередование битов двух половин), ломая эти макросы. На практике этого не произойдет. :- )

Вы можете попытаться сделать неприятный хак, но здесь есть проблема с эндианнесом. Что бы вы ни делали для преобразования, как компилятор должен знать, что вы хотите, чтобы floor была наиболее значимой частью результата, а fraction - менее значимой частью? Любое решение, основанное на переинтерпретации памяти, будет работать для одного конца, но не для другого.

Вы должны либо:

(1) Определите преобразование явно. Предполагая, что short равно 16 битам:

unsigned int val = (x.floor << 16) + x.fraction;

(2) Изменение Fixed таким образом, он имеет член int вместо двух шорт, а затем разлагает, когда требуется, а не , составляя, когда требуется.

Если вы хотите, чтобы сложение было быстрым, то (2) - это то, что нужно сделать. Если у вас 64-битный тип, то вы также можете выполнять умножение без разложения: 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 в вашу структуру, но, как сказано в другом ответе, это не переносится... и довольно уродливый.

Вы действительно хотите добавить отдельно каждое поле в отдельном методе? Вы хотите сохранить целое число из соображений производительности?

// add two Fixed 
Fixed operator+( Fixed a, Fixed b ) 
{   
...
}

//add Fixed and int
Fixed operator+( Fixed a, int b ) 
{   
...
}

Вы можете привести любой адресуемый тип к другому с помощью:

*(newtype *)&var