我的程序是关于一个给定 float 的方法,在这个方法中我想将这些 float 相乘或相加。但不是像 a * b 那样相乘,我想将这些 float 分解为它们的结构,例如符号位、指数的 8 位和尾数的其余位。
我想实现/模拟软件浮点加法和乘法(以了解有关 FP 硬件必须执行的操作的更多信息)。
<小时/>程序的头部有 segmentation :
#define SIGN(x) (x>>31);
#define MANT(x) (x&0x7FFFFF);
#define EXPO(x) ((x>>23)&0xFF);
#define SPLIT(x, s, m, e) do { \
s = SIGN(x); \
m = MANT(x); \
e = EXPO(x); \
if ( e != 0x00 && e != 0xFF ) { \
m |= 0x800000; \
} \
} while ( 0 )
#define BUILD(x, s, m, e) do { \
x = (s << 31) | (e<<23) | (m&0x7FFFFF); \
} while ( 0 )
主要如下:
float f = 2.3;
float g = 1.8;
float h = foo(&f, &g);
计算方法如下:
float foo(float *a, float *b) {
uint32_t ia = *(unsigned int *)a;
uint32_t ib = *(unsigned int *)b;
uint32_t result = 0;
uint32_t signa, signb, signr;
uint32_t manta, mantb, mantr;
uint32_t expoa, expob, expor;
SPLIT(ia, signa, manta, expoa);
SPLIT(ib, signb, mantb, expob);
我已经尝试通过添加指数并乘以尾数来进行乘法,如下所示:
expor = (expoa -127) + (expob -127) + 127;
mantr = (manta) * (mantb);
signr = signa ^ signb;
新 float 的返回和重建:
BUILD(result, signr, mantr, expor);
return *(float *)&result;
现在的问题是,结果是错误的。 mantr 甚至需要一个非常低的负数(如果 foo 得到 1.5 和 2.4 mantr 需要 -838860800 并且结果是 2.0000000)。
最佳答案
你不能只截断尾数乘法的结果,你需要取前 24 位(在使用低半部分进行舍入之后)并重新标准化(调整指数)。
浮点运算保留最高有效位。整数乘积的最高有效部分是高位;低位是小数点后更远的位。 (术语:它是“二进制点”,而不是“小数点”,因为二进制 float 使用基数 2(二进制),而不是 10(十进制)。)
<小时/>对于标准化输入,输入尾数中的隐式前导 1
表示用于实现 24 x 24 => 48 的 32x32 => 64 位 uint64_t
产品位尾数乘法的高位将位于 2 个可能位置之一,因此您不需要位扫描来找到它。比较或单位测试就可以了。
对于次正规输入,不能保证这一点,因此您需要检查 MSB 在哪里,例如使用 GNU C __builtin_clzll
。 (有许多特殊情况需要处理一个或两个输入不正常,和/或输出不正常。)
参见https://en.wikipedia.org/wiki/Single-precision_floating-point_format有关 IEEE-754 二进制 32 格式的更多信息,包括有效数字的隐含前导 1。
并且请参阅@njuffa的答案,了解实际测试的+工作实现,由于某种原因,该实现将64位操作作为两个32位的一半,而不是让C高效地完成。
<小时/>此外,return *(float *)&result;
违反了严格别名。仅在 MSVC 上安全。在 C99/C11 中使用 union 或 memcpy 进行类型双关。
关于c - 如何正确实现 float 乘法(软件FP),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55551307/