我正在寻找一个通常可以理解的表示法来定义一个不动点数表示。该表示法应该能够定义两个因子的幂(使用小数位)和一个通用因子(有时我被迫使用它,尽管效率较低)。还应该定义一个可选的偏移量。
我已经知道了一些可能的符号,但它们似乎都受限于特定的应用程序。
还请参阅维基百科讨论。
问题只是关于表示法(不是表示法的效率,也不是定点操作)。因此,这是一个代码可读性、可维护性和可测试性问题。
发布于 2014-03-15 11:51:53
啊,是的。拥有良好的命名注解是绝对关键的,不能用不动点算法引入bug。我使用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),代码在这些文件之间移动。例如,这很有可能被忽略,并引入示例2中存在的bug,这个错误在这里是故意执行的,并且不可能被意外执行。
发布于 2014-03-15 11:07:50
实际上,Q格式是商业应用程序中最常用的表示形式:当您需要快速处理小数时,您的处理器没有浮点单元( FPU ),它不能本地使用浮点数和双数据类型--它必须为它们模拟非常昂贵的指令。
通常,您只使用Q格式来表示小数部分,虽然这不是必须的,但是表示的精度更高。下面是你需要考虑的问题:
示例:假设您希望以Q15格式表示0.3;应用三的规则:
1 = 2^15 = 32768 = 0x8000
0.3 = X
-------------
X = 0.3*32768 = 9830 = 0x666这样做就失去了精确性,但至少现在的计算速度很快。
发布于 2014-03-15 10:57:25
在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++类的开销非常相似。使用模板会将一些决策移回编译时,而代价是失去灵活性。
这种基于对象的方法可能是最灵活的方法,它允许您以透明的方式添加对非二进制点位置的支持。
https://stackoverflow.com/questions/22422557
复制相似问题