[英]Packing 32bit floats into 30 bits (c++)
以下是我正在努力实现的目标:
我想这个问题包括两部分:
1)我可以简单地清除尾数的最低有效位吗? 我试过这个,到目前为止它有效,但也许我是在自找麻烦......像:
float f;
int packed = (*(int*)&f) & ~3;
// later
f = *(float*)&packed;
2)如果存在 1)失败的情况,那么实现这一目标的最快方法是什么?
提前致谢
这些重新解释强制转换实际上违反了严格的别名规则(C++ 标准的第 3.10 节)。 当您打开编译器优化时,这可能会在您面前炸开。
C++ 标准,第 3.10 节第 15 段说:
如果程序尝试通过以下类型之一以外的左值访问对象的存储值,则行为未定义
- 对象的动态类型,
- 对象的动态类型的 cv 限定版本,
- 类似于对象的动态类型的类型,
- 一种类型,它是与对象的动态类型对应的有符号或无符号类型,
- 一种类型,它是与对象的动态类型的 cv 限定版本相对应的有符号或无符号类型,
- 在其成员中包含上述类型之一的聚合或联合类型(递归地包括子聚合或包含联合的成员),
- 一个类型,它是对象的动态类型的(可能是 cv 限定的)基类类型,
- char 或 unsigned char 类型。
具体来说,3.10/15 不允许我们通过 unsigned int 类型的左值访问浮点对象。 我真的被这个咬了。 我写的程序在打开优化后停止工作。 显然,GCC 不希望 float 类型的左值与 int 类型的左值别名,这是 3.10/15 的公平假设。 优化器在利用 3.10/15 的 as-if 规则下对指令进行了调整,并且它停止工作。
在以下假设下
你应该可以这样做:
/// returns a 30 bit number
unsigned int pack_float(float x) {
unsigned r;
std::memcpy(&r,&x,sizeof r);
return r >> 2;
}
float unpack_float(unsigned int x) {
x <<= 2;
float r;
std::memcpy(&r,&x,sizeof r);
return r;
}
这不会受到“3.10 违规”的影响,并且通常非常快。 至少 GCC 将 memcpy 视为一个内在函数。 如果您不需要这些函数来处理 NaN、无穷大或具有极高量级的数字,您甚至可以通过将“r >> 2”替换为“(r+1) >> 2”来提高准确性:
unsigned int pack_float(float x) {
unsigned r;
std::memcpy(&r,&x,sizeof r);
return (r+1) >> 2;
}
即使由于尾数溢出而改变指数,这也有效,因为 IEEE-754 编码将连续浮点值映射到连续整数(忽略 +/- 零)。 这种映射实际上很好地近似了对数。
对于少数不寻常的 NaN 编码,盲目丢弃浮点数的 2 个 LSB 可能会失败。
NaN 被编码为 exponent=255, mantissa!=0,但 IEEE-754 没有说明应该使用哪个尾数值。 如果尾数值 <= 3,您可以将 NaN 变成无穷大!
您应该将它封装在一个结构体中,这样您就不会意外地将标记浮点数的用法与常规的“无符号整数”混合使用:
#include <iostream>
using namespace std;
struct TypedFloat {
private:
union {
unsigned int raw : 32;
struct {
unsigned int num : 30;
unsigned int type : 2;
};
};
public:
TypedFloat(unsigned int type=0) : num(0), type(type) {}
operator float() const {
unsigned int tmp = num << 2;
return reinterpret_cast<float&>(tmp);
}
void operator=(float newnum) {
num = reinterpret_cast<int&>(newnum) >> 2;
}
unsigned int getType() const {
return type;
}
void setType(unsigned int type) {
this->type = type;
}
};
int main() {
const unsigned int TYPE_A = 1;
TypedFloat a(TYPE_A);
a = 3.4;
cout << a + 5.4 << endl;
float b = a;
cout << a << endl;
cout << b << endl;
cout << a.getType() << endl;
return 0;
}
不过我不能保证它的便携性。
您需要多少精度? 如果 16 位浮点数就足够了(对于某些类型的图形就足够了),那么 ILM 的 16 位浮点数(“一半”),OpenEXR 的一部分很棒,遵守各种规则(http://www.openexr.com/ ),并且在将其打包到结构中后您将有足够的空间。
另一方面,如果您知道他们将要取的值的大致范围,您应该考虑定点。 它们比大多数人意识到的更有用。
我不能选择任何答案作为确定的答案,因为它们中的大多数都有有效的信息,但不是我想要的。 所以我只是总结一下我的结论。
我在问题的第 1 部分中发布的转换方法在 C++ 标准中显然是错误的,因此应该使用其他方法来提取浮点数。
最重要的是......据我从阅读有关 IEEE754 浮点数的响应和其他来源中了解到,可以从尾数中删除最低有效位。 它只会影响精度,只有一个例外:sNaN。 由于 sNaN 由设置为 255 的指数表示,并且尾数 != 0,因此可能存在尾数 <= 3 的情况,并且丢弃最后两位会将 sNaN 转换为 +/-Infinity。 但是由于 sNaN 不是在 CPU 上的浮点运算期间生成的,因此在受控环境下是安全的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.