【问题标题】:Divide by power of 2 resulting in float除以 2 的幂得到浮点数
【发布时间】:2013-04-09 12:59:08
【问题描述】:

我发现自己需要计算 16 位无符号整数除以 2 的幂,结果应该是 32 位浮点数(标准 IEEE 格式)。这是在嵌入式系统上,并且该例程被重复使用,所以我正在寻找比(float)x/(float)(1<<n) 更好的东西。此外,C 编译器非常有限(没有数学库、位域、reinterpret_cast 等)。

【问题讨论】:

  • 我怀疑你可以在没有汇编的情况下变得更好,除非有一些编译器内在函数。还有,个人资料,个人资料,个人资料。
  • 我认为您的意思是“比 (float)x/float(y) 好,其中 y 是 2 的幂”?还有你所说的“更好”是什么意思?
  • 我正在寻找“更好”的性能

标签: c floating-point bit-manipulation


【解决方案1】:

如果您不介意一些操作,那么显而易见的方法是将整数转换为浮点数,然后从指数位中减去 n 以实现除以 2^n:

y = (float)x;                          // convert to float
uint32_t yi = *(uint32_t *)&y);        // get float value as bits
uint32_t exponent = yi & 0x7f800000;   // extract exponent bits 30..23
exponent -= (n << 23);                 // subtract n from exponent
yi = yi & ~0x7f800000 | exponent;      // insert modified exponent back into bits 30..23
y = *(float *)&yi;                     // copy bits back to float

请注意,对于 x = 0,这会失败,因此您应该在转换前检查 x > 0。

总成本是一次 int-float 转换加上少量整数位/算术运算。如果您使用联合,则可以避免使用单独的 int/float 表示,而直接在 float 上工作。

【讨论】:

  • 谢谢。我也在考虑类似的事情,但不知道如何进行铸造。我想你的意思是 uint32_t exponent = yi & 0x7f800000;再次感谢!!!
  • 抱歉 - 是的 - 错字已修复。
  • 我这里没有我的系统,所以我还不能检查。但是,我应该担心一些编译器会在我们执行 y = (float)x 时对 x 进行规范化(例如,x = 15,然后 1.5E+1 存储在 y 中而不是 15E+0 中)。然后,修改指数部分就不能正常工作了。
  • 正如写的那样,不需要提取指数来做减法;它可以就地完成:yi -= n &lt;&lt; 23;。但是,当x 为零时,此代码将失败。
  • 如果编译器支持复合文字,则可以通过uint32_t yi = (union { float f; uint32_t u; }) { y } .u 重新解释浮点数位,并使用y = (union { uint32_t u; float f; }) { yi } .f 重新解释它们。如果编译器不支持复合文字,则可以定义一个联合对象以获得相同的效果。这避免了违反 C 标准未定义的指针别名规则。
【解决方案2】:

使用ldexpf(x, -n)。这个函数是由 C 标准定义的,可以完全做你想做的,返回 x•2-n,所以任何体面的编译器都会提供好的为此的代码。 (这需要数学库的一部分或将其优化为内联代码的编译器。)

如果n在编译时已知,你也可以考虑x * (1.f/(1&lt;&lt;n))。一个好的编译器会在编译时计算(1.f/(1&lt;&lt;n)),因此可执行代码将是两个操作:将x 转换为float 并乘以一个常数。如果编译器没有优化ldexpf(x, -n),这可能比为ldexpf(x, -n) 生成的代码要快。

【讨论】:

  • 除了用户指定的“此外,C 编译器非常有限(没有数学库、位字段、reinterpret_cast 等)之外,这将是一个很好的答案。”并且这个函数在数学库中。跨度>
【解决方案3】:

一个快速简单的解决方案是预先计算一个浮点值表 2-nn &gt;= 0n 的上限是多少,大约 31?)然后乘以 @987654324 @由表格的第 nth 元素。

如果您的代码模拟浮点乘法,这可能不是最快的,因为 CPU 不直接支持它。

但是,您可以只使用整数数学更快地完成。

示例(假设 IEEE-754 32 位浮点数):

#include <limits.h>
#include <string.h>
#include <stdio.h>

#define C_ASSERT(expr) extern char CAssertExtern[(expr)?1:-1]

C_ASSERT(CHAR_BIT == 8);
C_ASSERT(sizeof(float) == 4);
C_ASSERT(sizeof(int) == 4);

float div(int x, unsigned n)
{
  float res;
  unsigned e = 0;
  unsigned sign = x < 0;
  unsigned m = sign ? -x : x;  

  if (m)
  {
    while (m >= (1u << 24))
      m >>= 1, e++;

    while (m < (1u << 23))
      m <<= 1, e--;

    e += 0x7F + 23;

    e -= n; // divide by 1<<n

    m ^= 1u << 23; // reset the implicit 1

    m |= (e & 0xFF) << 23; // mix in the exponent

    m |= sign << 31; // mix in the sign
  }

  memcpy(&res, &m, sizeof m);

  return res;
}

void Print4Bytes(unsigned char buf[4])
{
  printf("%02X%02X%02X%02X ", buf[3], buf[2], buf[1], buf[0]);
}

int main(void)
{
  int x = 0x35AA53;
  int n;
  for (n = 0; n < 31; n++)
  {
    float v1 = (float)x/(1u << n);
    float v2 = div(x, n);
    Print4Bytes((void*)&v1);
    printf("%c= ", "!="[memcmp(&v1, &v2, sizeof v1) == 0]);
    Print4Bytes((void*)&v2);
    printf("%14.6f %14.6f\n", v1, v2);
  }
  return 0;
}

输出(ideone):

4A56A94C == 4A56A94C 3517011.000000 3517011.000000
49D6A94C == 49D6A94C 1758505.500000 1758505.500000
4956A94C == 4956A94C  879252.750000  879252.750000
48D6A94C == 48D6A94C  439626.375000  439626.375000
4856A94C == 4856A94C  219813.187500  219813.187500
47D6A94C == 47D6A94C  109906.593750  109906.593750
4756A94C == 4756A94C   54953.296875   54953.296875
46D6A94C == 46D6A94C   27476.648438   27476.648438
4656A94C == 4656A94C   13738.324219   13738.324219
45D6A94C == 45D6A94C    6869.162109    6869.162109
4556A94C == 4556A94C    3434.581055    3434.581055
44D6A94C == 44D6A94C    1717.290527    1717.290527
4456A94C == 4456A94C     858.645264     858.645264
43D6A94C == 43D6A94C     429.322632     429.322632
4356A94C == 4356A94C     214.661316     214.661316
42D6A94C == 42D6A94C     107.330658     107.330658
4256A94C == 4256A94C      53.665329      53.665329
41D6A94C == 41D6A94C      26.832664      26.832664
4156A94C == 4156A94C      13.416332      13.416332
40D6A94C == 40D6A94C       6.708166       6.708166
4056A94C == 4056A94C       3.354083       3.354083
3FD6A94C == 3FD6A94C       1.677042       1.677042
3F56A94C == 3F56A94C       0.838521       0.838521
3ED6A94C == 3ED6A94C       0.419260       0.419260
3E56A94C == 3E56A94C       0.209630       0.209630
3DD6A94C == 3DD6A94C       0.104815       0.104815
3D56A94C == 3D56A94C       0.052408       0.052408
3CD6A94C == 3CD6A94C       0.026204       0.026204
3C56A94C == 3C56A94C       0.013102       0.013102
3BD6A94C == 3BD6A94C       0.006551       0.006551
3B56A94C == 3B56A94C       0.003275       0.003275

【讨论】:

  • 我感觉这对于 2 的补码系统的最小值会失败。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-08
  • 1970-01-01
  • 2019-11-23
  • 1970-01-01
相关资源
最近更新 更多