![](/img/trans.png)
[英]How to Multiply a Floating Point Number using Bitwise Operators Without the Multiplication Operator in C
[英]Multiply float by a number using bitwise operators
我有這個函數,它將float
( f
)的位作為uint32_t
。 它應該使用位運算和 + 來計算f * 2048
並且應該將該值的位作為uint32_t
返回。
如果結果太大而無法表示為float
,則應返回+inf
或-inf
; 如果f
是+0
、 -0
、 +inf
或-inf
或Nan
,則應原樣返回。
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.