将 32 位浮点数打包为 30 位 (c++)
-
27-09-2019 - |
题
以下是我想要实现的目标:
- 我需要将 32 位 IEEE 浮点数打包为 30 位。
- 我想通过将尾数的大小减少 2 位来实现此目的。
- 操作本身应该尽可能快。
- 我知道会损失一些精度,这是可以接受的。
- 如果此操作不会破坏 SNaN、QNaN、无穷大等特殊情况,那将是一个优势。但我已经准备好为了速度而牺牲这一点。
我想这个问题由两部分组成:
1)我可以简单地清除尾数的最低有效位吗?我已经尝试过了,到目前为止它有效,但也许我是在自找麻烦......就像是:
float f;
int packed = (*(int*)&f) & ~3;
// later
f = *(float*)&packed;
2)如果存在 1)失败的情况,那么实现这一目标的最快方法是什么?
提前致谢
解决方案 4
我无法选择任何答案作为明确的答案,因为它们中的大多数都有有效的信息,但不完全是我想要的。所以我只是总结一下我的结论。
根据 C++ 标准,我在问题的第 1) 部分中发布的转换方法显然是错误的,因此应该使用其他方法来提取浮点数。
最重要的是...据我通过阅读有关 IEEE754 浮点数的响应和其他来源了解到,可以从尾数中删除最低有效位。它主要只会影响精度,但有一个例外:sNaN。由于 sNaN 由设置为 255 的指数表示,并且尾数 != 0,因此可能存在尾数 <= 3 的情况,并且删除最后两位会将 sNaN 转换为 +/-Infinity。但由于 sNaN 不是在 CPU 浮点运算期间生成的,因此在受控环境下是安全的。
其他提示
实际上,这些重新解释的强制转换违反了严格的别名规则(C++ 标准第 3.10 节)。当您打开编译器优化时,这可能会在您面前爆炸。
C++ 标准,第 3.10 节第 15 段说:
如果程序尝试通过以下类型之一以外的左值访问对象的存储值,则行为未定义
- 对象的动态类型,
- 对象动态类型的 cv 限定版本,
- 类似于对象的动态类型的类型,
- 与对象的动态类型相对应的有符号或无符号类型,
- 与对象动态类型的 cv 限定版本相对应的有符号或无符号类型,
- 聚合或联合类型,其成员中包含上述类型之一(递归地包括子聚合或包含联合的成员),
- 是对象动态类型的(可能是 cv 限定的)基类类型的类型,
- char 或 unsigned char 类型。
具体来说,3.10/15 不允许我们通过 unsigned int 类型的左值访问 float 对象。其实我自己也被这个咬过。我写的程序在打开优化后停止工作。显然,GCC 并不期望 float 类型的左值对 int 类型的左值进行别名,这在 3.10/15 中是一个合理的假设。优化器根据利用 3.10/15 的 as-if 规则对指令进行了改组,并且停止工作。
在以下情况下 假设
- float 实际上对应于 32 位 IEEE-float,
- sizeof(float)==sizeof(int)
- unsigned int 没有填充位或陷阱表示
你应该能够这样做:
/// 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 编码将连续的浮点值映射到连续的整数(忽略 +/- 0)。该映射实际上非常接近对数。
对于少量异常 NaN 编码,盲目删除浮点数的 2 个 LSB 可能会失败。
NaN 被编码为 exponent=255, mantissa!=0,但 IEEE-754 没有说明应使用哪些 mantissa 值。如果尾数值 <= 3,您可以将 NaN 变成无穷大!
您应该将其封装在一个结构中,这样您就不会意外地将标记浮点数与常规“unsigned int”的使用混合在一起:
#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/ ),将其打包到结构中后,您将剩余足够的空间。
另一方面,如果您知道它们将采用的值的大致范围,则应该考虑定点。它们比大多数人意识到的更有用。