用例:
我有一些包含浮点常量的大型数据数组。 生成定义该数组的文件,并且可以轻松修改模板。
我想做一些测试,降低精度如何影响结果的质量,以及二进制的可压缩性。
由于除了生成的文件外,我不想更改其他源代码,因此我正在寻找降低常量精度的方法。
我想将尾数限制为固定的位数(将较低的位数设置为0)。但是由于浮点字面量为十进制,所以存在一些困难,即以二进制表示形式在较低的尾数位中包含全零的方式指定数字。
最好的情况是:
#define FP_REDUCE(float) /* some macro */
static const float32_t veryLargeArray[] = {
FP_REDUCE(23.423f), FP_REDUCE(0.000023f), FP_REDUCE(290.2342f),
// ...
};
#undef FP_REDUCE
这应该在编译时完成,并且应该与平台无关。
答案 0 :(得分:2)
您要的内容可以通过不同程度的部分可移植性来完成,但不是绝对的,除非您想在构建时通过自己的预处理工具运行源文件以降低精度。如果这是您的选择,那可能是您最好的选择。
简短地说,我将至少假设您的浮点类型为2,并且遵循附件F / IEEE语义。这个应该是一个合理的假设,但是后者对于在默认标准符合性配置文件下具有扩展精度的平台(包括32位x86)上的gcc是错误的;您需要-std=cNN
或-fexcess-precision=standard
对其进行修复。
一种方法是将选择的2的幂相加或相减,以使舍入到所需的精度:
#define FP_REDUCE(x,p) ((x)+(p)-(p))
不幸的是,这是以绝对精度而非相对精度工作的,并且需要知道特定p
的正确值x
,该值将等于前2个基数的值x
的乘积,乘以2到FLT_MANT_DIG
的乘方减去所需的精度。该值不能作为用作初始化程序的常量表达式求值,但是可以使用FLT_EPSILON
来编写,如果可以假定为C99 +,则可以使用预处理器令牌粘贴来形成十六进制浮点常量,从而得出此因子的正确值。但是对于x
的前两位,您仍然需要知道2的幂。我看不出有什么方法可以将其提取为常量表达式。
编辑:我相信这是可以解决的,因此不需要绝对精度,而是自动缩放到该值,但这取决于进行中的工作的正确性。参见Is there a correct constant-expression, in terms of a float, for its msb?。如果可行,我稍后将结果与该答案结合起来。
我喜欢的另一种方法是,如果您的编译器在静态初始化程序中支持复合文字,并且可以采用IEEE类型表示形式,则使用联合并掩盖位:
union { float x; uint32_t r; } fr;
#define FP_REDUCE(x) ((union fr){.r=(union fr){x}.r & (0xffffffffu<<n)}.x)
其中n
是要删除的位数。这将舍入为零而不是最接近;如果要使其舍入到最接近的值,应该可以在屏蔽之前在低位添加一个适当的常量,但是您必须注意当加法溢出到指数位时会发生什么。
答案 1 :(得分:2)
以下内容使用Veltkamp-Dekker splitting algorithm从 x 中删除 n 位(四舍五入),其中 p = 2 n (例如,要删除八位,请为第二个参数使用0x1p8f
)。强制转换为float32_t
强制将结果强制转换为该类型,因为C标准允许实现在表达式中使用更高的精度。 (理论上,双取整可能会产生错误的结果,但是当float32_t
是IEEE基本的32位二进制格式,并且C实现以该格式或64位格式或更宽的格式计算该表达式时,这种情况就不会发生,因为前者是理想的格式,而后者足够宽,可以准确地表示中间结果。)
假定采用IEEE-754二进制浮点,且取整为最近。如果 x •( p +1)舍入为无穷大,则会发生溢出。
#define RemoveBits(x, p) (float32_t) (((float32_t) ((x) * ((p)+1))) - (float32_t) (((float32_t) ((x) * ((p)+1))) - (x))))