定点表示的表示法

时间:2014-03-15 10:30:25

标签: c fixed-point

我正在寻找一种通常可理解的符号来定义固定点数表示。 符号应该能够定义二次幂因子(使用小数位)和一般因子(有时我被迫使用它,虽然效率较低)。并且还应定义可选的偏移量 我已经知道一些可能的符号,但所有这些符号似乎都受限于特定的应用程序。

  • 例如,Simulink符号完全符合我的需要,但仅在Simulink世界中才知道。此外,fixdt()函数的重载使用不太可读。

  • TI定义了一个非常紧凑的Q Formats,但该符号是隐含的,并且它不管理通用因子(即不是2的幂)。

  • ASAM使用具有二次分子和分母多项式(COMPU_METHOD)的通用6系数有理函数。非常通用,但不那么友好。

另见Wikipedia讨论。

问题只是关于符号(不是表示的效率,也不是定点操作)。所以这是代码可读性,可维护性和可测试性的问题。

3 个答案:

答案 0 :(得分:2)

实际上,Q格式是商业应用程序中最常用的表示形式:您使用的是当您需要处理小数字FAST而您的处理器没有FPU(浮点单元)时,它本身不能使用float和double数据类型 - 它必须模仿非常昂贵的指令。

通常你使用Q格式来表示小数部分,尽管这不是必须的,你可以获得更高的表示精度。以下是您需要考虑的事项:

  • 您使用的位数(Q15使用16位数据类型,通常为短整数)
  • 第一位是符号位(16位中有15位,数据值为15)
  • 其余位用于存储您号码的小数部分。
  • 因为您要表示小数,所以您的值在[0,1)
  • 你也可以选择对整数部分使用一些位,但是你会失去精度 - 例如,如果你想用Q格式表示3.3,你需要1位用于符号,2位用于整数部分,以及对于小数部分保留13位(假设您使用16位表示) - >这种格式称为2Q13

示例:假设您想要以Q15格式表示0.3;你适用三法则:

      1 = 2^15 = 32768 = 0x8000
    0.3 = X
    -------------
    X = 0.3*32768 = 9830 = 0x666

你这样做会失去精确度,但至少计算速度很快。

答案 1 :(得分:2)

啊,是的。拥有良好的naming annotations绝对关键,不会引入定点算术错误。我使用Q表示法的显式版本来处理 通过将_Q<M>_<N>附加到变量的名称,在M和N之间进行任何划分。这也使得包括签名也成为可能。对此没有运行时性能惩罚。例如:

uint8_t length_Q2_6;                // unsigned, 2 bit integer, 6 bit fraction
int32_t sensor_calibration_Q10_21;  // signed (1 bit), 10 bit integer, 21 bit fraction.

/*
 * Calculations with the bc program (with '-l' argument):
 *
 * sqrt(3)
 * 1.73205080756887729352
 *
 * obase=16
 * sqrt(3)
 * 1.BB67AE8584CAA73B0
 */
const uint32_t SQRT_3_Q7_25 = 1 << 25 | 0xBB67AE85U >> 7; /* Unsigned shift super important here! */

如果有人还没有完全理解为什么这样的注释非常重要, 您能否发现以下两个示例中是否存在错误?

示例1:

speed_fraction = fix32_udiv(25, speed_percent << 25, 100 << 25);
squared_speed  = fix32_umul(25, speed_fraction, speed_fraction);
tmp1 = fix32_umul(25, squared_speed, SQRT_3);
tmp2 = fix32_umul(12, tmp1 >> (25-12), motor_volt << 12);

示例2:

speed_fraction_Q7_25 = fix32_udiv(25, speed_percent << 25, 100 << 25);
squared_speed_Q7_25  = fix32_umul(25, speed_fraction_Q7_25, speed_fraction_Q7_25);
tmp1_Q7_25  = fix32_umul(25, squared_speed_Q7_25, SQRT_3_Q1_31);
tmp2_Q20_12 = fix32_umul(12, tmp1_Q7_25 >> (25-12), motor_volt << 12);

想象一下,如果一个文件包含#define SQRT_3 (1 << 25 | 0xBB67AE85U >> 7)而另一个文件包含#define SQRT_3 (1 << 31 | 0xBB67AE85U >> 1),并且代码在这些文件之间移动。例如1,这很有可能被忽视,并引入了示例2中存在的错误,这是故意做的,并且没有意外完成的可能性。

答案 2 :(得分:1)

在C中,您不能使用内置类型的用户定义类型。如果你想这样做,你需要使用C ++。在该语言中,您可以为定点类型定义一个类,重载所有算术运算符(+, - ,*,/,%,+ =, - =,* =,/ =,%=, - ,++ ,转换为其他类型),以便这个类的实例的使用真的像内置类型。

在C中,您需要明确地执行您想要的操作。有两种基本方法。


方法1:在用户代码中进行定点调整 这是无开销的,但您需要记住进行正确的调整。我相信,最简单的方法就是将过去的点位数添加到变量名的末尾,因为类型系统对你没有太大帮助,即使你typedef'点你所有的点位置使用。这是一个例子:

int64_t a_7 = (int64_t)(7.3*(1<<7));    //a variable with 7 past point bits
int64_t b_5 = (int64_t)(3.78*(1<<5));   //a variable with 5 past point bits
int64_t sum_7 = a_7 + (b_5 << 2);    //to add those two variables, we need to adjust the point position in b
int64_t product_12 = a_7 * b_5;    //the product produces a number with 12 past point bits

当然,这很麻烦,但至少你可以轻松检查点调整是否正确。


方法2:为定点数定义一个结构,并在一堆函数中封装算法。像这样:

typedef struct FixedPoint {
    int64_t data;
    uint8_t pointPosition;
} FixedPoint;

FixedPoint fixed_add(FixedPoint a, FixedPoint b) {
    if(a.pointPosition >= b.PointPosition) {
        return (FixedPoint){
            .data = a.data + (b.data << a.pointPosition - b.pointPosition),
            .pointPosition = a.pointPosition
        };
    } else {
        return (FixedPoint){
            .data = (a.data << b.pointPosition - a.pointPosition) + b.data,
            .pointPosition = b.pointPosition
        };
    }
}

这种方法在使用上稍微清晰一些,但是,它会带来很大的开销。开销包括:

  1. 函数调用。

  2. 复制参数和结果传递的结构,或者如果使用指针则指针取消引用。

  3. 需要在运行时计算点数调整。

  4. 这与没有模板的C ++类的开销非常相似。使用模板会将一些决策转回编译时,但会失去灵活性。

    这种基于对象的方法可能是最灵活的方法,它允许您以透明的方式添加对非二进制点位置的支持。