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);
}
Rohan Saha
  • 15
  • 4
  • What do you do with the quotient to interpret or output it as -3277.9? – Ian Abbott Nov 01 '19 at 16:07
  • The bug is in your display function. – Ian Abbott Nov 01 '19 at 16:14
  • @IanAbbott Thanks for helping me. I have added the code snippet I am using to display the number. The display function works for other arithmetic operators. Could you please help me figure out the bug? – Rohan Saha Nov 01 '19 at 16:20
  • `printf ("Number = %.5f\n", num / 65536.0);` – njuffa Nov 01 '19 at 16:23
  • @njuffa Yep, that fixed it. Thanks :D Could you please briefly explain me or give some hints on what I was doing wrong in my original code? – Rohan Saha Nov 01 '19 at 16:29
  • @njuffa Thank you. I was actually writing to avoid floating point arithmetic because I am writing this library for ARM Cortex-M7 that doesn't have a FPU. Therefore, floating point arithmetic makes it much slower. Would there be a quicker way to do it without using floating point arithmetic? – Rohan Saha Nov 01 '19 at 16:49

1 Answers1

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);
}
Ian Abbott
  • 15,083
  • 19
  • 33
  • If rounding the output is preferred: `(fraction * 100000 + (1 << 15)) >> 16` – njuffa Nov 01 '19 at 17:52
  • @njuffa That's a good idea, although it won't make any difference for -32761 / 10 because the actual value stored for the quotient is approximately -3276.09999084. The only way round it would be to round to 4 decimal places. – Ian Abbott Nov 01 '19 at 18:05
  • @njuffa `num` will be -214702489 for -3276.1, so `num/65536.0` will be -3276.099990844726562. `printf("%.5f\n", num/65536.0);` will display -3276.09999. – Ian Abbott Nov 01 '19 at 18:53
  • @njuffa correction: `num/65536.0` will be -3276.0999908447265625 exactly (I missed the 5 off the end), but it doesn't make any difference to the output. – Ian Abbott Nov 01 '19 at 19:03
  • @IanAbbott Thank you very much for the detailed answer. That makes sense. Once, you have actually broken it down showing the bit representation, it has become really clear to follow and debug. Thanks! :D – Rohan Saha Nov 01 '19 at 19:25
  • This passed an exhaustive test vs `printf ("%.04f\n", num);` – njuffa Nov 02 '19 at 03:12