簡體   English   中英

使用按位運算符將浮點數乘以數字

[英]Multiply float by a number using bitwise operators

我有這個函數,它將floatf )的位作為uint32_t 它應該使用位運算和 + 來計算f * 2048並且應該將該值的位作為uint32_t返回。

如果結果太大而無法表示為float ,則應返回+inf-inf 如果f+0-0+inf-infNan ,則應原樣返回。

uint32_t float_2048(uint32_t f) {
    uint32_t a = (f << 1) ;

    int result = a << 10;

    return result;
}

這是我到目前為止所擁有的,但如果我給它賦值“1”,它會返回 0 而不是 2048。我該如何解決這個問題?

一些示例輸入和輸出:

./float_2048 1
2048
./float_2048 3.14159265
6433.98193
./float_2048 -2.718281828e-20
-5.56704133e-17
./float_2048 1e38
inf

正如評論中提到的,要將浮點數乘以 2 的冪(假設它很可能以IEEE-754 格式表示),我們可以將該冪添加到(二進制)指數部分表示。

對於單精度(32 位) float值,該指數存儲在位 30-23 中,以下代碼顯示如何提取這些值,添加所需的值(11,因為 2048 = 2 11 ),然后替換指數具有該修改值的位。

uint32_t fmul2048(uint32_t f)
{
    #define EXPONENT 0x7F800000u
    #define SIGN_BIT 0x80000000u
    uint32_t expon = (f & EXPONENT) >> 23; // Get exponent value
    f &= ~EXPONENT;  // Remove old exponent
    expon += 11;     // Adding 11 to exponent multiplies by 2^11 (= 2048);
    if (expon > 254) return EXPONENT | (f & SIGN_BIT); // Too big: return +/- Inf
    f |= (expon << 23); // Insert modified exponent
    return f;
}

毫無疑問,會有一些“小技巧”可以用來使代碼更小和/或更高效; 但我避免這樣做是為了保持代碼清晰。 我還包括了一個錯誤檢查(對於太大的指數),如果該測試失敗,代碼將返回 +/- Infinity 的標准表示(所有指數位設置為 1,並保持原始符號)。 (我將其他錯誤檢查作為“讀者練習”。)

處理所有float需要更多代碼。

做一些測試,以便代碼可以假設預期的float大小,匹配字節序和(IEEE)編碼。 C不需要將float作為 32 位,將字節序與整數匹配,而不是二進制 32編碼,盡管這很常見。

提取有偏指數並尋找它的最小值和最大值。

最大值表示 NAN 或無窮大。

最小值是亞法線和零,需要特殊處理。 有效數字需要移動。 如果該結果現在是正常float ,請重新編碼。

簡單之間的偏差指數需要一個增量並測試超過FLT_MAX的指數。

已成功測試所有float

#include <assert.h>
#include <stdint.h>

static_assert(sizeof(uint32_t) == sizeof(float), "Unexpected float size");

#define IEEE_MASK_BIASED_EXPO     0x7F800000u
#define IEEE_MASK_BIASED_EXPO_LSB 0x00800000u
#define IEEE_MASK_SIGNIFICAND     0x007FFFFFu
#define IEEE_SIGNIFICAND_MAX      0x00FFFFFFu
#define IEEE_INFINITY             0x7F800000u

// Scale value by 2048
uint32_t float_2048(uint32_t f) {
  uint32_t expo = f & IEEE_MASK_BIASED_EXPO;
  // Test for infinity or NAN
  if (expo == IEEE_MASK_BIASED_EXPO) {
    return f;
  }
  // Sub-normal and zero test
  if (expo == 0) {
    uint64_t sig = f & IEEE_MASK_SIGNIFICAND;
    sig <<= 11; // *= 2048;
    // If value now a normal one
    if (sig > IEEE_MASK_SIGNIFICAND) {
      expo += IEEE_MASK_BIASED_EXPO_LSB;
      while (sig > IEEE_SIGNIFICAND_MAX) {
        sig >>= 1;
        expo += IEEE_MASK_BIASED_EXPO_LSB;
      }
      f = (f & ~IEEE_MASK_BIASED_EXPO) | (expo & IEEE_MASK_BIASED_EXPO);
    }
    f = (f & ~IEEE_MASK_SIGNIFICAND) | (sig & IEEE_MASK_SIGNIFICAND);
  } else {
    expo += 11 * IEEE_MASK_BIASED_EXPO_LSB; // *= 2048;
    if (expo >= IEEE_MASK_BIASED_EXPO) {
      f &= ~(IEEE_MASK_BIASED_EXPO | IEEE_MASK_SIGNIFICAND);
      f |= IEEE_INFINITY;
    } else {
      f = (f & ~IEEE_MASK_BIASED_EXPO) | (expo & IEEE_MASK_BIASED_EXPO);
    }
  }
  return f;
}

測試代碼。

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

typedef union {
  uint32_t u32;
  float f;
} fu32;

int main(void ) {
  // Lightweight test to see if endian matches and IEEE encoding
  assert((fu32) {.u32 = 0x87654321}.f == -1.72477726182e-34f);
  float f[] = {0, FLT_TRUE_MIN, FLT_MIN, 1, FLT_MAX};
  size_t n = sizeof f/sizeof f[0];
  for (size_t i = 0; i<n; i++) {
    fu32 x = { .f = f[i] };
    float y0 = x.f * 2048.0f;
    fu32 y1 = { .u32 = float_2048(x.u32) };
    if (memcmp(&y0, &y1.f, sizeof y0)) {
      printf("%.9g %.9g\n", y0, y1.f);
    }
  }
  fu32 x = { .u32 = 0 };
  do {
    fu32 y0 = { .f = isnan(x.f) ? x.f : x.f * 2048.0f };
    fu32 y1 = { .u32 = float_2048(x.u32) };
    if (memcmp(&y0.f, &y1.f, sizeof y0)) {
      printf("%.9g %.9g\n", y0.f, y1.f);
      printf("%08lx %08lx %08lx\n", (unsigned long) x.u32,
          (unsigned long) y0.u32, (unsigned long) y1.u32);
      break;
    }
    x.u32++;
  } while (x.u32 != 0);
  puts("Done");
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM