在C中提取位和重构

时间:2015-11-29 17:15:59

标签: c binary floating-point ieee-754

对于类项目,我试图从IEEE浮点数中提取3位字段,将其乘以0.5,然后重新构建数字。我通过将它们推入无符号整数来完成提取工作:

x = 5.5
x: 1000000101100000000000000000000 (1085276160, 0x40b00000)
extracted sign: 0 (0, 0x0)
extracted exp: 10000001 (129, 0x81)
extracted sig: 01100000000000000000000 (1610612736, 0x60000000)

我的两个问题是:

a)如何将这些位重新组成原始输入?我试过了:

return sign ^ exp ^ sig

并没有得到正确的结果。

b)如何将数字乘以0.5?我是否乘以sig或exp?

2 个答案:

答案 0 :(得分:0)

a)你可以“但是这些位回来了”与使用ands(掩码)和ors(将它们重新组合在一起)的组合一起使用。使用程序员的计算器应该足以理解它是如何工作的。

例如(伪-C):

unsigned int sign = ...;
unsigned int exp  = ...;
unsigned int sig  = ...;

请参阅:float format

unsigned int out;
out = ((sign & 1)<<31)|((exp & 0xff)<<23)|(sig & 0x7fffff);

&amp;的重要性1,&amp; 0xff和&amp; 0x07fffff是它们各自分别设置了1位,8位和23位。

b)要么工作,要么将指数移动+ -1,则乘以/除以2。这不会降低“精度”,因为有效数字保持不变。如果指数已经处于最小值,那么唯一的方法就是开始减少signifcand,这将是非规范化。

注意:对于未存储的有效数字,存在隐含的高阶1位。换句话说,除非指数为零,否则存在第24位未存储且被假定为1位。

答案 1 :(得分:0)

  

a)如何将这些位重新组成原始输入?

要重新构建float的32位表示形式,请使用|,而不是^

#include <stdint.h>

#define FLT_SIGN_SFT 31
#define FLT_EXP_SFT  23
#define FLT_SIG_SFT  0
#define FLT_SIGN_MSK 1
#define FLT_EXP_MSK  0xFF
#define FLT_SIG_MSK  0x7FFFFF
#define FLT_SIG_IMPLIED_BIT  (0x7FFFFF + 1)

float float_form(uint32_t sign, uint32_t exp, uint32_t sig) {
  union {
    uint32_t u32;
    float f;
  } u;
  u.u32 = sign << FLT_SIGN_SFT | exp << FLT_EXP_SFT | sig << FLT_SIG_SFT;
  return u.f;
}

Endian关注:只要floatuint32_t都是相同的endian,就可以了。需要进行其他调整。

  

如何将数字乘以0.5?我是否乘以sig或exp?

这很棘手。如果x是NaN或Infinty,则不执行任何操作。如果x在正常数字中,则递减指数(特殊情况:如果值现在是次正常)。如果x是次正常,则移位有效数字。如果1位被移出,请考虑舍入。如果舍入导致指数变化,请调整。

float float_div2(float f) {
  union {
    uint32_t u32;
    float f;
  } u;
  u.f = f;
  uint32_t sign = (u.u32 >> FLT_SIGN_SFT) & FLT_SIGN_MSK;
  uint32_t exp = (u.u32 >> FLT_EXP_SFT) & FLT_EXP_MSK;
  uint32_t sig = (u.u32 >> FLT_SIG_SFT) & FLT_SIG_MSK;
  if (exp < FLT_EXP_MSK) {
    unsigned shift_out = 0;
    if (exp > 0) {
      exp--;
      if (exp == 0) {
        sig += FLT_SIG_IMPLIED_BIT;
        shift_out = sig % 2u;
        sig /= 2;
      }
    } else {
      shift_out = sig % 2u;
      sig /= 2;
    }
    if (shift_out > 0) {
      assert(exp == 0);
      // Assume round to even
      if (sig % 2) {
        sig++;
        if (sig >= FLT_SIG_IMPLIED_BIT) {
          sig -= FLT_SIG_IMPLIED_BIT;
          exp++;
        }
      } // end if (sig % 2)
    } // end if (exp > 0)
  } // end if (exp < FLT_EXP_MSK)
  return float_form(sign, exp, sig);
}

测试代码。针对所有float成功测试。

void float_div2_test(float x) {
  float y = x / 2.0f;
  float z = float_div2(x);
  if (memcmp(&y, &z, sizeof z)) {
    printf("%.10e %.10e %.10e\n", x, y, z);
    printf("%a %a %a\n", x, y, z);
    exit(1);
  }
}

void float_div2_tests() {
  union {
    uint32_t u32;
    float f;
  } u;
  u.u32 = 0;
  do {
    u.u32--;
    float_div2_test(u.f);
  } while (u.u32);
  puts("Success!");
}