繁体   English   中英

将 32 位浮点数打包成 30 位 (c++)

[英]Packing 32bit floats into 30 bits (c++)

以下是我正在努力实现的目标:

  • 我需要将 32 位 IEEE 浮点数打包成 30 位。
  • 我想通过将尾数的大小减少 2 位来做到这一点。
  • 操作本身应该尽可能快。
  • 我知道会丢失一些精度,这是可以接受的。
  • 如果这个操作不会破坏像 SNaN、QNaN、无穷大等特殊情况,那将是一个优势。但我准备牺牲这个速度。

我想这个问题包括两部分:

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 规则下对指令进行了调整,并且它停止工作。

在以下假设下

  • 浮点数实际上对应于 32 位 IEEE 浮点数,
  • 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 编码将连续浮点值映射到连续整数(忽略 +/- 零)。 这种映射实际上很好地近似了对数。

对于少数不寻常的 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM