使用按位运算符模拟C中的浮点乘法[关闭]

问题描述 投票:3回答:4

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

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

c binary bit-manipulation multiplication
4个回答
3
投票

必须使用以下运算符将乘以2的数字相乘:<< >> | &〜^

因为我们给出了一个无符号长度来模拟具有单点精度的浮点值,所以我们应该处理所有可以模拟的值。 ref

首先让我们假设浮点数被编码为binary32,而unsigned是32位。 C不需要其中任何一个。

首先隔离指数以处理float子组:亚正常,正常,无穷大和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;
}

1
投票

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

此代码乘以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


1
投票

这是一个使用+运算符的简单代码。它并没有假装涵盖浮点详细说明的所有方面。此解决方案向您显示单个精度浮点的esponent递增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


1
投票

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

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

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

下面包含的测试框架详尽地测试了fpmul_by_2(),这在现代PC上只需要几分钟。我在运行Windows的x64平台上使用了英特尔编译器。

#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;
}
© www.soinside.com 2019 - 2024. All rights reserved.