2

我创建了一个结构来表示一个定点正数。我希望小数点两边的数字包含 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 xFixed y。为此,我将它们视为整数并相加。

(Fixed) ( (int)x + (int)y );

但正如我的 Visual Studio 2010 编译器所说,我无法在 和 之间进行Fixed转换int

这样做的正确方法是什么?

编辑:我不致力于{short floor, short fraction}实施固定。

4

9 回答 9

6

你可以尝试一个讨厌的黑客,但这里有一个字节序的问题。无论您做什么转换,编译器应该如何知道您想要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));

这将适用于大端系统,其中 Fixed 没有填充(并且整数类型没有填充位)。在小端系统上,您需要 Fixed 的成员处于另一个顺序,这就是它讨厌的原因。有时通过 memcpy 进行投射是正确的做法(在这种情况下,它是一种“技巧”而不是“讨厌的 hack”)。这不是那些时代之一。

于 2010-10-26T11:50:34.277 回答
5

如果必须,您可以使用联合,但要注意字节序问题。您可能会发现算术不起作用,而且肯定是不可移植的。

typedef struct Fixed_t {
   union { 
        struct { unsigned short floor; unsigned short fraction }; 
        unsigned int whole;
         };
} Fixed;

这更有可能(我认为)工作大端(Windows/英特尔不是)。

于 2010-10-26T11:42:52.043 回答
3

一些魔法:

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]

关键点:

  • 我使用固定大小的整数类型,因此您不必依赖short16 位和int32 位。
  • Floor和的宏Fraction(大写以避免与floor()函数冲突)以与字节序无关的方式访问这两个部分,如foo.Floorfoo.Fraction

编辑:应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。在 little-endian 系统上,此值为 1,因此((Fixed){1}).d==1计算为 1(真)并((Fixed){1}).d!=1计算为0(假)。在大端系统上,情况正好相反。

因此,在 little-endian 系统上,Floorisw[1]Fractionis w[0]。在大端系统上,Floorisw[0]Fractionis w[1]。无论哪种方式,您最终都会为平台的字节序存储/访问正确的一半 32 位值。

理论上,假设系统可以对 16 位和 32 位值使用完全不同的表示(例如,将两半的位交错),从而破坏这些宏。在实践中,这不会发生。:-)

于 2010-10-26T15:54:01.143 回答
2

这是不可移植的,因为编译器不保证 aFixed将使用与int. 正确的方法是定义一个函数Fixed add(Fixed a, Fixed b)

于 2010-10-26T11:39:38.817 回答
1

只需单独添加碎片。您需要知道表示“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;
}
于 2010-10-26T11:44:41.390 回答
0

试试这个:

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;
}
于 2010-10-26T11:42:59.527 回答
0

如果您的编译器将这两个短于 4 个字节,那么您可以使用 memcpy 将您的 int 复制到您的结构中,但正如在另一个答案中所说,这不是可移植的......而且非常难看。

您真的关心在单独的方法中分别添加每个字段吗?出于性能原因,您要保留整数吗?

于 2010-10-26T11:45:44.480 回答
-1
// add two Fixed 
Fixed operator+( Fixed a, Fixed b ) 
{   
...
}

//add Fixed and int
Fixed operator+( Fixed a, int b ) 
{   
...
}
于 2010-10-26T12:00:58.370 回答
-1

您可以使用以下方法将任何可寻址类型转换为另一种类型:

*(newtype *)&var
于 2010-10-26T14:53:03.480 回答