How to perform division on two 32Q16 fixed-point integers?

0

I am trying to divide two 32Q16 numbers using fixed-point processing arithmetic. What I understand is that when we divide one 32Q16 fixed-point operand by another, we require the result to be a 32Q16 number. We, therefore, need a 64Q32 dividend, which is created by sign extending the original 32Q16 dividend, and then left-shifting by 16 bits. The division is then done with a 64-bit dividend and a 32-bit divisor, to give a 32-bit quotient.

I have written this C routine to calculate the quotient. The division works fine for positive numbers, but when I try to divide any negative number, the result is incorrect.

I am not sure how C handles a division between 64-bit integer and 32-bit integer. Could anyone please help me understand what I am doing wrong? Thank you.

int32_t divide32Q16 (int32_t dividend, int32_t divisor)

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

  return quotient;
}

When I try to divide -32761 by 10, the expected result should be -3276.1 but the actual result I got from my code is -3277.9

Here is the code snippet I am using to display the number:

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

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

  printf("Number = %d.%" PRIu64 "\n", integer, fraction);
}
c
bit-shift
fixed-point
asked on Stack Overflow Nov 1, 2019 by Rohan Saha • edited Nov 1, 2019 by Rohan Saha

1 Answer

2

The display function defined by OP is currently as follows:

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

However, that does not display negative fractions properly because the 2's complement representation of a negative number has most of the bits inverted when compared to the positive number of the same magnitude. It also only prints 4 decimal digits for the fractional part, which isn't as precise as it could be.

For example, -3276.1 is represented as the int32_t value -214702489 (-3276.1 * 65536 with the fractional part discarded). That has the same bit pattern as 0xF333E667. The display function determines the integer part to be printed by arithmetic shifting the value left by 16 bits, represented by the bit pattern 0xFFFFF333, and truncating to 16 bits, represented by the bit pattern 0xF333. As a signed integer, that bit pattern corresponds to -3277. The fractional part to be printed is determined by taking the low 16 bits, represented by the bit pattern 0xE667, corresponding to the decimal number 58983, multiplying by 10000, resulting in 589830000, and shifting right 16 bits (dividing by 65536), resulting in 9000. Therefore, the value is printed as -3277.9000.

Here is a function that displays the number properly, using 5 fractional digits:

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);
}

Note that the original number resulting from -32761 / 10 will be displayed as -3276.09999 because the fractional part 0.1 cannot be represented exactly in binary.

It may be preferable to round to 4 decimal places as follows, and this also removes the need for 64 bit numbers.

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);
}
answered on Stack Overflow Nov 1, 2019 by Ian Abbott • edited Nov 1, 2019 by Ian Abbott

User contributions licensed under CC BY-SA 3.0