【问题标题】:How to perform division on two 32Q16 fixed-point integers?如何对两个 32Q16 定点整数进行除法?
【发布时间】:2020-02-27 20:49:13
【问题描述】:

我正在尝试使用定点处理算法将两个 32Q16 数字相除。我的理解是,当我们将一个 32Q16 定点操作数除以另一个时,我们要求结果是一个 32Q16 数。因此,我们需要一个 64Q32 被除数,它是通过符号扩展原始 32Q16 被除数,然后左移 16 位创建的。然后用 64 位除数和 32 位除数进行除法,得到 32 位商。

我编写了这个 C 例程来计算商。除法适用于正数,但当我尝试除任何负数时,结果不正确。

我不确定 C 如何处理 64 位整数和 32 位整数之间的除法。谁能帮我理解我做错了什么?谢谢。

int32_t divide32Q16 (int32_t dividend, int32_t divisor)

{
  int32_t quotient = ((int64_t) dividend << 16) / divisor;

  return quotient;
}

当我尝试将 -32761 除以 10 时,预期结果应该是 -3276.1,但我从代码中得到的实际结果是 -3277.9

这是我用来显示数字的代码 sn-p:

void display32Q16 (int32_t num)
{
  int16_t integer = num >> 16;

  uint64_t fraction = ((0x0000FFFF & num) * 10000) >> 16;

  printf("Number = %d.%" PRIu64 "\n", integer, fraction);
}

【问题讨论】:

  • 商如何解释或输出为-3277.9?
  • 这个bug在你的显示函数中。
  • @IanAbbott 感谢您帮助我。我添加了用于显示数字的代码 sn-p。显示功能适用于其他算术运算符。你能帮我找出错误吗?
  • printf ("Number = %.5f\n", num / 65536.0);
  • @njuffa 是的,解决了它。谢谢 :D 能否请您简要解释一下或提示我在原始代码中做错了什么?

标签: c bit-shift fixed-point


【解决方案1】:

OP定义的显示函数目前如下:

void display32Q16 (int32_t num)
{
    int16_t integer = num >> 16;
    uint64_t fraction = ((0x0000FFFF & num) * 10000) >> 16;
    printf("Number = %d.%" PRIu64 "\n", integer, fraction);
}

但是,这不能正确显示负分数,因为与相同幅度的正数相比,负数的 2 的补码表示具有大部分位反转。它也只打印小数部分的 4 位十进制数字,这并不像它应该的那样精确。

例如,-3276.1 表示为 int32_t 值 -214702489(-3276.1 * 65536,小数部分被丢弃)。这与 0xF333E667 具有相同的位模式。显示函数通过将值左移 16 位(由位模式 0xFFFFF333 表示)并截断为 16 位(由位模式 0xF333 表示)来确定要打印的整数部分。作为有符号整数,该位模式对应于 -3277。要打印的小数部分由取低 16 位确定,由位模式 0xE667 表示,对应十进制数 58983,乘以 10000,得到 589830000,再右移 16 位(除以 65536),得到9000。因此,该值打印为-3277.9000。

这是一个正确显示数字的函数,使用 5 个小数位:

void display32Q16 (int32_t num)
{
    int32_t integer = num >> 16;
    uint64_t fraction = num & 0xFFFF;
    const char *xtrasign = "";

    if (integer < 0 && fraction != 0)
    {
        integer++;
        fraction = 65536 - fraction;
    }
    fraction = (fraction * 100000) >> 16;
    if (num < 0 && integer == 0)
    {
        /* special case for number between -1 and 0 */
        xtrasign = "-";
    }
    printf("Number = %s%" PRIi32 ".%05" PRIu64 "\n", xtrasign, integer, fraction);
}

请注意,从 -32761 / 10 得到的原始数字将显示为 -3276.09999,因为小数部分 0.1 不能以二进制精确表示。

最好如下四舍五入到小数点后 4 位,这也消除了对 64 位数字的需要。

void display32Q16 (int32_t num)
{
    int32_t integer = num >> 16;
    uint32_t fraction = num & 0xFFFF;
    const char *xtrasign = "";

    if (integer < 0 && fraction != 0)
    {
        integer++;
        fraction = 65536 - fraction;
    }
    fraction = ((fraction * 10000) + (1 << 15)) >> 16;
    if (fraction >= 10000)
    {
        /* deal with fraction rounding overflow */
        if (num < 0)
            integer--;
        else
            integer++;
        fraction -= 10000;
    }
    if (num < 0 && integer == 0)
    {
        /* special case for number between -1 and 0 */
        xtrasign = "-";
    }
    printf("Number = %s%" PRIi32 ".%04" PRIu32 "\n", xtrasign, integer, fraction);
}

【讨论】:

  • 如果首选舍入输出:(fraction * 100000 + (1 &lt;&lt; 15)) &gt;&gt; 16
  • @njuffa 这是个好主意,尽管它对 -32761 / 10 没有任何影响,因为为商存储的实际值大约是 -3276.09999084。四舍五入的唯一方法是四舍五入到小数点后 4 位。
  • @njuffa num 对于 -3276.1 将是 -214702489,所以 num/65536.0 将是 -3276.099990844726562。 printf("%.5f\n", num/65536.0); 将显示 -3276.09999。
  • @njuffa 更正:num/65536.0 将是 -3276.0999908447265625(我错过了最后的 5),但它对输出没有任何影响。
  • @IanAbbott 非常感谢您的详细回答。这就说得通了。曾经,您实际上已将其分解为位表示,因此可以非常清楚地遵循和调试。谢谢! :D
猜你喜欢
  • 1970-01-01
  • 2014-01-14
  • 1970-01-01
  • 1970-01-01
  • 2019-10-21
  • 2014-01-10
  • 1970-01-01
  • 2010-09-06
相关资源
最近更新 更多