【问题标题】:Simulating Floating Point Multiplication in C using Bitwise Operators [closed]使用位运算符在 C 中模拟浮点乘法 [关闭]
【发布时间】:2019-02-09 21:16:57
【问题描述】:

我必须编写一个模拟浮点乘法的程序。对于这个程序,我们假设一个单精度浮点数存储在unsigned long a 中。我必须仅使用以下运算符将a 中存储的数字乘以 2:<< >> | & ~ ^

我了解这些运算符的功能,但我对如何实现它的逻辑感到困惑。任何帮助将不胜感激。

【问题讨论】:

  • 在这里写下你的第一个实现代码!你有什么疑问和/或问题!?
  • 乘以 2 表示需要在指数上加 1。为此,您需要了解format of a floating point number
  • 您确定不能使用加法吗? (请不要考虑我考虑数字+数字,我理解问题及其解决方案)
  • "模拟浮点乘法的程序。"代码是否需要处理所有 float 值?如果不是,代码需要模拟float 的哪个子集?
  • @chux 我相信既然给了我们一个unsigned long 来模拟具有单点精度的浮点值,我们应该处理所有可以模拟的事情

标签: c binary bit-manipulation multiplication


【解决方案1】:

必须仅使用以下运算符将存储在 a 中的数字乘以 2:> | &~^

由于我们被赋予一个无符号长整数来模拟具有单点精度的浮点值,因此我们应该处理所有可以模拟的事情。 ref

首先让我们假设浮点数编码为binary32,而unsigned 是32 位的。 C 不需要这些。

首先分离出处理float子组的指数:sub-normal、normal、infinity和NAN

以下是一些经过轻微测试的代码 - 我稍后会回顾,现在将其视为伪代码模板。

#define FLT_SIGN_MASK  0x80000000u
#define FLT_MANT_MASK  0x007FFFFFu
#define FLT_EXPO_MASK  0x7F800000u
#define FLT_EXPO_LESSTHAN_MAXLVAUE(e)   ((~(e)) & FLT_EXPO_MASK)
#define FLT_EXPO_MAX   FLT_EXPO_MASK
#define FLT_EXPO_LSBit 0x00800000u

unsigned increment_expo(unsigned a) {
  unsigned carry = FLT_EXPO_LSBit;
  do {
    unsigned sum = a ^ carry;
    carry = (a & carry) << 1;
    a = sum;
  } while (carry);
  return a;
}

unsigned float_x2_simulated(unsigned x) {
  unsigned expo = x & FLT_EXPO_MASK;
  if (expo) { // x is a normal, infinity or NaN
    if (FLT_EXPO_LESSTHAN_MAXLVAUE(expo)) { // x is a normal
      expo = increment_expo(expo);  // Double the number
      if (FLT_EXPO_LESSTHAN_MAXLVAUE(expo)) { // no overflow
        return (x & (FLT_SIGN_MASK | FLT_MANT_MASK)) | expo;
      }
      return (x & FLT_SIGN_MASK) | FLT_EXPO_MAX;
    }
    // x is an infinity or NaN
    return x;
  }
  // x is a sub-normal
  unsigned m = (x & FLT_MANT_MASK) << 1;  // Double the value
  if (m & FLT_SIGN_MASK) {
    // Doubling caused sub-normal to become normal
    // Special code not needed here and the "carry" becomes the 1 exponent.
  }
  return (x & FLT_SIGN_MASK) | m;
}

【讨论】:

  • 当然是紫外线,周日过得很慢?
  • @chqrlie 活在梦想中。
【解决方案2】:

这是我使用按位运算符的代码。

此代码将 单精度浮点数乘以 2 浮点指数加 1 并且仅使用位运算符;此外,还要处理指数和数字符号(第 30 位和第 31 位)。

它并没有假装涵盖浮点精化的所有方面。

请记住,如果第 30 位和/或第 31 位被代码更改,我们就会发生溢出。

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

#include <inttypes.h>

int main()
{
    float f = -23.45F;

    uint32_t *i=(uint32_t *)(&f);
    uint32_t sgn;
    uint32_t c,sc;

    printf("%08X %f\n",*i,f);

    sgn = *i & (0xC0000000); // copies bits 31 and 30

    c = *i & (1U<<23);
    *i ^= (1U<<23);

    while(c)
    {
        sc = c << 1;
        c = *i & sc;
        *i ^= sc;
    };

    if (sgn != *i & (0xC0000000)) {
       puts("Exponent overflow");
    }

    printf("%08X %f\n",*i,f);

    return 0;
}

另请参阅:Wikipedia Single-precision floating point

【讨论】:

  • 许多same short-comings - 可能对 OP 不太关心,但不是通用解决方案。
  • @chux 代码是一个例子,是为了演示如何增加浮点的指数,而不是涵盖浮点管理的所有方面。
  • 同意代码并没有涵盖所有方面,但是由于答案没有表达任何限制,我评论指出了各种不足。
【解决方案3】:

这是一个使用+ 运算符的简单代码。它并没有假装涵盖浮点精化的所有方面。该解决方案向您展示了将单精度浮点数的指数加 1,位 23-29(30 是指数符号),您将获得乘以 2。

此代码仅使用位运算符来考虑符号位并避免最终的指数溢出。

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

#include <inttypes.h>

int main()
    {
        float f = 23.45F;

        uint32_t *i=(uint32_t *)(&f);
        uint32_t app;

        printf("%08X %f\n",*i,f);

        app = *i & (0xC0000000); // copies bits 31 and 30
        *i += (1U<<23);
        *i &= ~(0xC0000000);     // leave bits 31 and 30
        *i |= app;               // set original bits 31 and 30


        printf("%08X %f\n",*i,f);

        return 0;
    }

另请参阅:Wikipedia Single-precision floating-point

【讨论】:

  • OP 还没有确认 + 是允许的,目前他说只有 "> | & ~ ^" 是
  • 你不想使用int exp = ... ; switch (exp) { ... case 23: exp = 24; break; case 24: exp = 25; break; ... } ...或等价物,太奇怪了^^
  • 允许&做掩码
  • @bruno 感谢大家的回答和帮助,但 Jo 是对的,不允许添加。这就是它让我如此困惑的原因之一,哈哈
  • FLT_MAX、NAN、亚法线附近的值可能为 0 时失败。抗锯齿问题以及便携性差。
【解决方案4】:

下面的函数fpmul_by_2() 实现了所需的功能,假设'unsigned long' 是32 位整数类型,'float' 是映射到IEEE-754 'binary32' 的32 位浮点类型。进一步假设我们要在禁用异常的情况下模拟 IEEE-754 乘法,从而产生标准规定的屏蔽响应。

使用了两个帮助函数,分别实现 32 位整数加法和相等比较。加法是基于二进制加法中和和进位的定义(详细解释见this previous question),而相等比较利用了(a^b) == 0iffa == b这一事实。

浮点参数的处理需要大致区分三类操作数:非正规和零、法线、无穷大和 NaN。法线的加倍是通过增加指数来实现的,因为我们在二进制浮点格式上进行操作。可能会发生溢出,在这种情况下必须返回无穷大。 Infinity 和 NaN 返回不变,除了 SNaN 被转换为 QNaN,这是 IEEE-754 规定的屏蔽响应。非正规和零是通过将有效数字加倍来处理的。对零、次正规和无穷大的处理可能会破坏符号位,因此参数的符号位被强制用于结果。

下面的测试框架对fpmul_by_2() 进行了详尽的测试,在现代PC 上只需几分钟。我在运行 Windows 的 x64 平台上使用了 Intel 编译器。

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

// assumptions:
// 'unsigned long' is a 32-bit type 
// 'float' maps to IEEE-754 'binary32'. Exceptions are disabled

// add using definition of sum and carry bits in binary addition
unsigned long add (unsigned long a, unsigned  long b)
{
    unsigned long sum, carry;
    carry = b;
    do {
        sum = a ^ carry;
        carry = (a & carry) << 1;
        a = sum;
    } while (carry);
    return sum;
}

// return 1 if a == b, else 0
int eq (unsigned long a, unsigned  long b)
{
    unsigned long t = a ^ b;
    // OR all bits into lsb
    t = t | (t >> 16);
    t = t | (t >>  8);
    t = t | (t >>  4);
    t = t | (t >>  2);
    t = t | (t >>  1);
    return ~t & 1;
}

// compute 2.0f * a
unsigned long fpmul_by_2 (unsigned long a)
{
    unsigned long expo_mask = 0x7f800000UL;
    unsigned long expo_lsb  = 0x00800000UL;
    unsigned long qnan_mark = 0x00400000UL;
    unsigned long sign_mask = 0x80000000UL;
    unsigned long zero      = 0x00000000UL;
    unsigned long r;

    if (eq (a & expo_mask, zero)) {             // subnormal or zero
        r = a << 1;                             // double significand
    } else if (eq (a & expo_mask, expo_mask)) { // INF, NaNs
        if (eq (a & ~sign_mask, expo_mask)) {   // INF
            r = a;
        } else {                                // NaN
            r = a | qnan_mark;                  // quieten SNaNs
        }
    } else {                                    // normal
        r = add (a, expo_lsb);                  // double by bumping exponent
        if (eq (r & expo_mask, expo_mask)) {    // overflow
            r = expo_mask;
        }
    }
    return r | (a & sign_mask);                 // result has sign of argument
}

float uint_as_float (unsigned long a)
{
    float r;
    memcpy (&r, &a, sizeof r);
    return r;
}

unsigned long float_as_uint (float a)
{
    unsigned long r;
    memcpy (&r, &a, sizeof r);
    return r;
}

int main (void)
{
    unsigned long res, ref, a = 0;
    do {
        res = fpmul_by_2 (a);
        ref = float_as_uint (2.0f * uint_as_float (a));
        if (res != ref) {
            printf ("error: a=%08lx  res=%08lx  ref=%08lx\n", a, res, ref);
            return EXIT_FAILURE;
        }
        a++;
    } while (a);
    printf ("test passed\n");
    return EXIT_SUCCESS;
}

【讨论】:

  • @chux 是的。脑袋放屁。现在将修复。
猜你喜欢
  • 1970-01-01
  • 2022-06-16
  • 2022-07-17
  • 1970-01-01
  • 2021-02-20
  • 1970-01-01
  • 2023-04-06
  • 1970-01-01
  • 2011-03-26
相关资源
最近更新 更多