将原始14位二进制补码转换为有符号16位整数

时间:2015-12-03 20:54:20

标签: c embedded avr signed

我在嵌入式C中做了一些工作,加速度计将数据作为14位2的补码数返回。我将此结果直接存储到uint16_t中。稍后在我的代码中,我试图将这种“原始”形式的数据转换为有符号整数,以表示/使用我的其余代码。

我无法让编译器了解我想要做什么。在下面的代码中,我正在检查第14位是否已设置(意味着数字为负),然后我想反转这些位并加1以得到数字的大小。

int16_t fxls8471qr1_convert_raw_accel_to_mag(uint16_t raw, enum fxls8471qr1_fs_range range) {
  int16_t raw_signed;
  if(raw & _14BIT_SIGN_MASK) {
    // Convert 14 bit 2's complement to 16 bit 2's complement
    raw |= (1 << 15) | (1 << 14); // 2's complement extension
    raw_signed = -(~raw + 1);
  }
  else {
    raw_signed = raw;
  }
  uint16_t divisor;
  if(range == FXLS8471QR1_FS_RANGE_2G) {
    divisor = FS_DIV_2G;
  }
  else if(range == FXLS8471QR1_FS_RANGE_4G) {
    divisor = FS_DIV_4G;
  }
  else {
    divisor = FS_DIV_8G;
  }

  return ((int32_t)raw_signed * RAW_SCALE_FACTOR) / divisor;
}

遗憾的是,此代码不起作用。反汇编告诉我,由于某种原因,编译器正在优化我的语句raw_signed = -(~raw + 1);我如何实现我想要的结果?

数学在纸上运行,但我觉得编译器因为某种原因与我抗争:(。

5 个答案:

答案 0 :(得分:12)

将14位2的补码值转换为16位有符号,同时保持该值只是一个符号:

int16_t accel = (int16_t)(raw << 2) / 4 ;

左移将符号位推入16位符号位位置,除以4会恢复幅度但保持其符号。除法避免了右移的实现定义行为,但通常会在允许的指令集上产生单个算术移位权。强制转换是必要的,因为raw << 2int表达式,除非int是16位,否则除法将简单地恢复原始值。

然而,将加速度计数据左移两位并将其视为传感器首先是16位就更简单了。将所有内容归一化为16位的好处是,如果使用任意数量的位数达到16的传感器,代码不需要更改。幅度将简单地增加四倍,最不重要的两位将为零 - 无信息获得或失去,任何情况下缩放都是任意的。

int16_t accel = raw << 2 ;

在这两种情况下,如果你想要无符号幅度,那么就是:

int32_t mag = (int32_t)labs( (int)accel ) ;

答案 1 :(得分:6)

我会做简单的算术。结果是14位有符号,表示为从0到2 ^ 14的数字.1。测试数字是否为2 ^ 13或更高(表示否定)然后减去2 ^ 14。

int16_t fxls8471qr1_convert_raw_accel_to_mag(uint16_t raw, enum fxls8471qr1_fs_range range) 
{
  int16_t raw_signed = raw;
  if(raw_signed >= 1 << 13) {
    raw_signed -= 1 << 14;
  }

  uint16_t divisor;
  if(range == FXLS8471QR1_FS_RANGE_2G) {
    divisor = FS_DIV_2G;
  }
  else if(range == FXLS8471QR1_FS_RANGE_4G) {
    divisor = FS_DIV_4G;
  }
  else {
    divisor = FS_DIV_8G;
  }

  return ((int32_t)raw_signed * RAW_SCALE_FACTOR) / divisor;
}

请检查我的算术。 (我有13和14正确吗?)

答案 2 :(得分:1)

假设您的特定C实现中的int是16位宽,则在修改(1 << 15)时使用的表达式raw会产生未定义的行为。在这种情况下,如果采用条件的分支来评估表达式,编译器可以自由地生成代码以执行任何操作 - 或者不执行任何操作。

如果int为16位宽,则表达式-(~raw + 1)和所有中间值将具有unsigned int == uint16_t类型。这是“通常的算术转换”的结果,因为(16位)int不能表示类型uint16_t的所有值。结果将设置为高位,因此超出类型int可表示的范围,因此将其分配给类型int的左值会产生实现定义的行为。您必须查阅您的文档,以确定它定义的行为是否符合您的预期和期望。

如果您改为执行14位符号转换,强制关闭高阶位((~raw + 1) & 0x3fff),则结果 - 所需负值的倒数 - 可由16位签名表示int,因此明确转换为int16_t的定义明确,并保留(正)值。你想要的结果是相反的结果,你可以通过否定它来获得。总体:

raw_signed = -(int16_t)((~raw + 1) & 0x3fff);

当然,如果int在您的环境中超过16位,那么我认为您的原始代码无法按预期工作的原因。但是,这不会使上面的表达式无效,无论默认值int的大小如何,都会产生一致定义的行为。

答案 3 :(得分:1)

假设代码到达return ((int32_t)raw_signed ...时,其值为[-8192 ... +8191]范围:

如果RAW_SCALE_FACTOR是4的倍数,那么可以节省一点钱。

所以而不是

int16_t raw_signed = raw << 2; 
raw_signed >>= 2;

代替

int16_t fxls8471qr1_convert_raw_accel_to_mag(uint16_t raw,enum fxls8471qr1_fs_range range){
  int16_t raw_signed = raw << 2;
  uint16_t divisor;
  ...
  // return ((int32_t)raw_signed * RAW_SCALE_FACTOR) / divisor;
  return ((int32_t)raw_signed * (RAW_SCALE_FACTOR/4)) / divisor;
}

答案 4 :(得分:0)

要将14位二进制补码转换为有符号值,可以翻转符号位并减去偏移量:

int16_t raw_signed = (raw ^ 1 << 13) - (1 << 13);