首页 > 解决方案 > 如何正确实现浮点数的乘法(软件 FP)

问题描述

我的程序是关于一个给定浮点数的方法,在这个方法中我想乘或加这些浮点数。但不要像 a * b 那样相乘,我想将这些浮点数分解为它们的结构,例如符号位、指数位的 8 位和尾数的其余位。

我想实现/模拟软件浮点加法和乘法(了解更多关于 FP 硬件必须做什么)。


在程序的头部有故障:

    #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;

新花车的归还与重建:

   BUILD(result, signr, mantr, expor);
   return *(float *)&result;

现在的问题是,结果是错误的。mantr 甚至需要一个非常低的负数(如果 foo 得到 1.5 和 2.4 mantr 需要 -838860800 并且结果是 2.0000000)。

标签: cfloating-pointmultiplicationadditionieee-754

解决方案


您不能只截断尾数乘法的结果,您需要取24 位(在使用低半部分进行舍入之后)并重新归一化(调整指数)。

浮点运算保留最高有效位。 整数乘积的最高位是高位;低位是小数点后更远的位置。(术语:它是“二进制点”,而不是“小数点”,因为二进制浮点数使用基数 2(二进制),而不是 10(十进制)。)


对于标准化输入,输入有效数字中的隐式前导1意味着用于实现 24 x 24 => 48 位尾数乘法的 32x32 => 64 位uint64_t乘积将在 2 个可能的位置之一具有其高位,因此您不要不需要位扫描即可找到它。比较或单位测试就可以了。

对于非正常输入,这不能保证,因此您需要检查 MSB 的位置,例如使用 GNU C __builtin_clzll。(有许多特殊情况需要处理一个或两个输入低于正常,和/或输出低于正常。

有关 IEEE-754 binary32 格式的更多信息,包括有效数字的隐含前导 1,请参阅https://en.wikipedia.org/wiki/Single-precision_floating-point_format

查看@njuffa对实际测试 + 工作实现的回答,该实现出于某种原因将 64 位操作作为两个 32 位半,而不是让 C 有效地做到这一点。


此外,return *(float *)&result;违反严格的别名。它只在 MSVC 上是安全的。在 C99 / C11 中使用 union 或 memcpy 进行类型双关。


推荐阅读