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

时间:2010-10-02 15:16:01

标签: c++ floating-point ieee-754 packing mantissa

以下是我要实现的目标:

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

我想这些问题包含两部分:

1)我可以简单地清除尾数的最低位吗?我试过这个,到目前为止它的确有效,但也许我在寻找麻烦......有点像:

float f;
int packed = (*(int*)&f) & ~3;
// later
f = *(float*)&packed;

2)如果有1)失败的情况,那么实现这一目标的最快方法是什么?

提前致谢

5 个答案:

答案 0 :(得分:10)

您实际上违反了严格的别名规则(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(浮点)==的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&gt;&gt; 2”替换为“(r + 1)&gt;&gt; 2”来提高准确性:

unsigned int pack_float(float x) {
    unsigned r;
    std::memcpy(&r,&x,sizeof r);
    return (r+1) >> 2;
}

即使由于尾数溢出而改变指数也是有效的,因为IEEE-754编码将连续的浮点值映射到连续的整数(忽略+/-零)。这种映射实际上非常接近对数。

答案 1 :(得分:8)

对于少量不寻常的NaN编码,盲目地丢弃浮点数的2个LSB可能会失败。

NaN编码为指数= 255,尾数!= 0,但IEEE-754没有说明应该使用哪个mantiassa值。如果尾数值<= 3,则可以将NaN变为无穷大!

答案 2 :(得分:2)

你应该将它封装在一个结构中,这样你就不会意外地将标记浮点数的用法与常规的“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;
}

我不能保证它的便携性。

答案 3 :(得分:1)

我不能选择任何答案作为明确的答案,因为他们中的大多数都有有效的信息,但不是我想要的。所以我只想总结一下我的结论。

我在问题的第1部分中发布的转换方法显然是错误的C ++标准,因此应该使用其他提取浮点数的方法。

最重要的......据我所知,通过阅读有关IEEE754浮点数的响应和其他来源,可以从尾数中删除最低有效位。它主要影响精度,但有一个例外:sNaN。由于sNaN由设置为255的指数表示,并且尾数!= 0,因此可能存在尾数将<= 3的情况,并且丢弃最后两位将将sNaN转换为+/-无穷大。但由于sNaN不是在CPU上的浮点运算期间生成的,因此在受控环境下是安全的。

答案 4 :(得分:1)

您需要多少精度?如果16位浮点数足够(足以用于某些类型的图形),那么ILM的16位浮点数(“一半”),OpenEXR的一部分很棒,遵守各种规则(http://www.openexr.com/ ),将它打包成结构后,你将有足够的空间。

另一方面,如果您知道它们将要采用的大致值范围,您应该考虑固定点。它们比大多数人意识到的更有用。