1

我正在做一个小项目,我需要浮点乘法与 16 位浮点数(半精度)。不幸的是,我在算法方面遇到了一些问题:

示例输出

1 * 5 = 5
2 * 5 = 10
3 * 5 = 14.5
4 * 5 = 20
5 * 5 = 24.5

100 * 4 = 100
100 * 5 = 482

源代码

const int bits = 16;
const int exponent_length = 5;
const int fraction_length = 10;

const int bias = pow(2, exponent_length - 1) - 1;
const int exponent_mask = ((1 << 5) - 1) << fraction_length;
const int fraction_mask = (1 << fraction_length) - 1;
const int hidden_bit = (1 << 10);  // Was 1 << 11 before update 1

int float_mul(int f1, int f2) {
    int res_exp = 0;
    int res_frac = 0;
    int result = 0;

    int exp1 = (f1 & exponent_mask) >> fraction_length;
    int exp2 = (f2 & exponent_mask) >> fraction_length;
    int frac1 = (f1 & fraction_mask) | hidden_bit;
    int frac2 = (f2 & fraction_mask) | hidden_bit;

    // Add exponents
    res_exp = exp1 + exp2 - bias;  // Remove double bias

    // Multiply significants
    res_frac = frac1 * frac2;   // 11 bit * 11 bit → 22 bit!
    // Shift 22bit int right to fit into 10 bit
    if (highest_bit_pos(res_mant) == 21) {
        res_mant >>= 11;
        res_exp += 1;
    } else {
        res_mant >>= 10;
    }
    res_frac &= ~hidden_bit;    // Remove hidden bit

    // Construct float
    return (res_exp << bits - exponent_length - 1) | res_frac;
}

顺便说一句:我将浮点数存储在整数中,因为稍后我会尝试将此代码移植到某种无浮点操作的汇编程序中。

问题

为什么代码仅适用于某些值?我是否忘记了一些标准化或类似的事情?还是它只是偶然起作用?

免责声明:我不是 CompSci 学生,这是一个休闲项目;)

更新#1

感谢Eric Postpischil的评论,我注意到代码存在一个问题:hidden_bit标志关闭了一个(应该是1 << 10)。随着这种变化,我不再得到小数位,但仍然有一些计算是关闭的(例如3•3=20)。我认为,这是res_frac答案中描述的转变。

更新#2

代码的第二个问题确实是res_frac转移。更新 #1 后,当我得到 22 位的frac1 * frac2. 我已经用更正的 shift 语句更新了上面的代码。感谢大家的每一个评论和回答!:)

4

3 回答 3

3

从粗略的看:

  • 没有尝试确定产品中高位的位置。两个 11 位的数字,每个都设置了高位,可以产生一个 21 位或 22 位的数字。(两位数的示例:10 2 •10 2是 100 2,三位,但 11 2 •11 2是 1001 2,四位。)
  • 结果被截断而不是四舍五入。
  • 标志被忽略。
  • 在输入或输出上不处理次正规数。
  • 11在一个地方硬编码为移位量。这可能是不正确的;正确的数量将取决于如何处理有效数字以进行归一化和舍入。
  • 在解码中,指数字段右移fraction_length. 在编码中,它左移bits - exponent_length - 1. 为避免错误,应在两个地方使用相同的表达式。

从chux更详细的角度来看:

  • res_frac = frac1 * frac2如果int小于 23 位(乘积为 22,符号为 1)则失败。
于 2013-08-28T16:08:17.680 回答
1

一个问题是您正在截断而不是四舍五入:

res_frac >>= 11;            // Shift 22bit int right to fit into 10 bit

您应该res_frac & 0x7ff首先计算您的算法即将丢弃的 22 位结果部分,并将其与0x400. 如果低于,则截断。如果高于,则从零四舍五入。如果它等于0x400,则舍入到偶数替代。

于 2013-08-28T16:10:53.190 回答
1

这更多是关于如何更轻松地使您的代码正确的建议,而不是分析现有代码的问题所在。

一些或所有浮点算术运算有许多共同的步骤。我建议将每一个提取到一个函数中,该函数可以专注于一个问题来编写,并单独测试。然后,当您开始编写例如乘法时,您只需要处理该操作的细节。

使用具有实际有符号指数的结构和更宽的无符号整数字段中的完整有效数字,所有操作都将更容易。如果您正在处理带符号的数字,它还将有一个用于符号位的布尔值。

以下是一些示例操作,它们可能是单独的函数,至少在你让它工作之前:

unpack:取一个 16 位浮点数并将指数和有效数提取到一个结构中。

pack: Undo unpack - 处理删除隐藏位,将偏差应用于指数,并将它们组合成一个浮点数。

normalize:移动有效位并调整指数以将最高有效 1 位带到指定的位位置。

回合:应用您的四舍五入规则以删除低重要性位。如果你想进行 IEEE 754 风格的舍入到最近,你需要一个保护位,它是将被丢弃的最高有效位,以及一个额外的位,指示是否有任何一个位的重要性低于保护位。

于 2013-08-28T18:44:39.497 回答