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

Smi*_*ver 4 c++ floating-point packing mantissa ieee-754

以下是我要实现的目标:

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

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

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

float f;
int packed = (*(int*)&f) & ~3;
// later
f = *(float*)&packed;
Run Code Online (Sandbox Code Playgroud)

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

提前致谢

sel*_*tze 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;
}
Run Code Online (Sandbox Code Playgroud)

这不会受到"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;
}
Run Code Online (Sandbox Code Playgroud)

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


小智 8

盲目地丢弃浮点数的2个LSB可能会因少量不寻常的NaN编码而失败.

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