Assembly mod algorithm on processor with no division operator

前端 未结 8 1889
挽巷
挽巷 2020-12-09 10:57

I need to implement a simple macro that finds the modulo of two numbers on a processor that doesn\'t have a division operator (think ARM). I could use division by repeated s

相关标签:
8条回答
  • 2020-12-09 11:24

    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.

    0 讨论(0)
  • 2020-12-09 11:26

    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)

    0 讨论(0)
  • 2020-12-09 11:26

    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.

    0 讨论(0)
  • 2020-12-09 11:30

    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.

    0 讨论(0)
  • 2020-12-09 11:37

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

    0 讨论(0)
  • 2020-12-09 11:43

    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
    }
    
    0 讨论(0)
提交回复
热议问题