我正在寻找一种通常可理解的符号来定义固定点数表示。 符号应该能够定义二次幂因子(使用小数位)和一般因子(有时我被迫使用它,虽然效率较低)。并且还应定义可选的偏移量 我已经知道一些可能的符号,但所有这些符号似乎都受限于特定的应用程序。
例如,Simulink符号完全符合我的需要,但仅在Simulink世界中才知道。此外,fixdt()函数的重载使用不太可读。
TI定义了一个非常紧凑的Q Formats,但该符号是隐含的,并且它不管理通用因子(即不是2的幂)。
ASAM使用具有二次分子和分母多项式(COMPU_METHOD)的通用6系数有理函数。非常通用,但不那么友好。
另见Wikipedia讨论。
问题只是关于符号(不是表示的效率,也不是定点操作)。所以这是代码可读性,可维护性和可测试性的问题。
答案 0 :(得分:2)
实际上,Q格式是商业应用程序中最常用的表示形式:您使用的是当您需要处理小数字FAST而您的处理器没有FPU(浮点单元)时,它本身不能使用float和double数据类型 - 它必须模仿非常昂贵的指令。
通常你使用Q格式来表示小数部分,尽管这不是必须的,你可以获得更高的表示精度。以下是您需要考虑的事项:
示例:假设您想要以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
};
}
}
这种方法在使用上稍微清晰一些,但是,它会带来很大的开销。开销包括:
函数调用。
复制参数和结果传递的结构,或者如果使用指针则指针取消引用。
需要在运行时计算点数调整。
这与没有模板的C ++类的开销非常相似。使用模板会将一些决策转回编译时,但会失去灵活性。
这种基于对象的方法可能是最灵活的方法,它允许您以透明的方式添加对非二进制点位置的支持。