Assembly mod algorithm on processor with no division operator

隐身守侯 提交于 2019-11-28 08:26:27

No idea what exact operations you are limited to, but I'd think you'd do long division, something like this, in pseudo-code:

dividend = abs(dividend)
divisor = abs(divisor)
if divisor == 0,
    barf
remainder = dividend
next_multiple = divisor

do
    multiple = next_multiple
    next_multiple = left_shift(multiple, 1)
while next_multiple <= remainder && next_multiple > multiple

while multiple >= divisor,
    if multiple <= remainder,
        remainder = remainder - multiple
    multiple = right_shift(multiple, 1)

To actually calculate the quotient (or at least its absolute value), the last part would be something like:

quotient = 0
while multiple >= divisor,
    quotient = left_shift(quotient, 1);
    if multiple <= remainder,
        remainder = remainder - multiple
        quotient = quotient + 1
    multiple = right_shift(multiple, 1)

None of this is tested, and it is probably riddled with errors.

I can think of two possible approaches. Because this is homework I will just mention them and let you work if they are feasible and how to implement them:

  1. A/B = 2^(log2(A)-log2(b)): If you can get the logarithm of the values, you can closely approximate the division.

  2. Binary Long Division: You learned how to do decimal long division before you could do division, right? So teach your computer to do binary long division (it should actually be easier in binary).

(edit: corrected #1., the log division equation)

This doesn't answer your question directly but is an interesting case nonetheless. If the number is being modulo'd by a power of two the operation can be performed as

x % 2^n = x & (2^n - 1)

Which uses a single AND operation, which usually is a one or two cycle operation.

More information At Wikipedia

Seems like subtracting (or adding if a is negative) by b until you hit or cross 0 would be an easy implementation albeit almost certainly not the most efficient.

Jweede, I had no idea how to solve your problem but I found a seemingly relevent post here.

A/B=Q, therefore A=B*Q. We know both A & B, we want Q.

My idea to add to the mix: Binary search Q. Start with Q=0 & Q=1, perhaps as base cases. Keep doubling until B * Q > A, and then you've got two bounds (Q and Q/2), so find the correct Q between the two of those. O(log(A/B)), but a bit trickier to implement:

#include <stdio.h>
#include <limits.h>
#include <time.h>

// Signs were too much work.
// A helper for signs is easy from this func, too.
unsigned int div(unsigned int n, unsigned int d)
{
    unsigned int q_top, q_bottom, q_mid;
    if(d == 0)
    {
        // Ouch
        return 0;
    }

    q_top = 1;
    while(q_top * d < n && q_top < (1 << ((sizeof(unsigned int) << 3) - 1)))
    {
        q_top <<= 1;
    }
    if(q_top * d < n)
    {
        q_bottom = q_top;
        q_top = INT_MAX;
    }
    else if(q_top * d == n)
    {
        // Lucky.
        return q_top;
    }
    else
    {
        q_bottom = q_top >> 1;
    }

    while(q_top != q_bottom)
    {
        q_mid = q_bottom + ((q_top - q_bottom) >> 1);
        if(q_mid == q_bottom)
            break;

        if(d * q_mid == n)
            return q_mid;
        if(d * q_mid > n)
            q_top = q_mid;
        else
            q_bottom = q_mid;
    }
    return q_bottom;
}

int single_test(int n, int d)
{
    int a = div(n, d);
    printf("Single test: %u / %u = %u\n", n, d, n / d);
    printf(" --> %u\n", a);
    printf(" --> %s\n", a == n / d ? "PASSED" : "\x1b[1;31mFAILED\x1b[0m");
}

int main()
{
    unsigned int checked = 0;
    unsigned int n, d, a;

    single_test(1389797028, 347449257);
    single_test(887858028, 443929014);
    single_test(15, 5);
    single_test(16, 4);
    single_test(17, 4);
    single_test(0xFFFFFFFF, 1);

    srand(time(NULL));

    while(1)
    {
        n = rand();
        d = rand();

        if(d == 0)
            continue;

        a = div(n, d);
        if(n / d == a)
            ++checked;
        else
        {
            printf("\n");
            printf("DIVISION FAILED.\n");
            printf("%u / %u = %u, but we got %u.\n", n, d, n / d, a);
        }

        if((checked & 0xFFFF) == 0)
        {
            printf("\r\x1b[2K%u checked.", checked);
            fflush(stdout);
        }
    }

    return 0;
}

Additionally, you can also iterate through the bits, setting each one to 1. If B * Q <= A is true, keep the bit as 1, otherwise zero it. Proceed MSB->LSB. (You will need to be able to detect it B*Q will overflow, however.

Thanks for the advice all!

I started using a simple division by repeated subtraction algorithm to implement this. But as pointed out by ysth, there's a much easier way. Here's the first algorithm:

        .macro mod a, b, r
        mov a, r
divlp:  sub r, b, r
        cmp r, b
        bge divlp
        .endmacro

This closely resembles:

mod(a, b){
   int r = a
   while(r >= b){
      r = r - b
   }
   return r
}

mod can be computed bit by bit:

int r = 0;
int q = 0;
for (int i = sizeof(n) * 8 - 1; i >= 0; --i) {
  r <<= 1;
  r |= (n >> i) & 1;
  if (r > d) {
    r -= d;
    q |= 1 << i;
  }
}
return r;

That gives you the remainder, q would be the quotient. If you have bsrl instruction, you can set a better high bound for i, since you can start at the most significant bit only.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!