【问题标题】:Assembly mod algorithm on processor with no division operator没有除法运算符的处理器上的汇编 mod 算法
【发布时间】:2009-06-02 05:40:12
【问题描述】:

我需要实现一个简单的宏,它可以在没有除法运算符的处理器上找到两个数字的模(想想 ARM)。我可以通过重复减法来使用除法,但我不知道这是否是最有效或最容易使用的。

有什么建议吗?代码会更有帮助。这个特殊的类让我们使用 SPARC 的一个子集,所以大多数操作看起来像这样:add r1, r2, rdest

这个特定的赋值要求检查a mod b == 0 或除法的余数为零。因此,任何有关有效实施的提示或建议都将受到欢迎。

【问题讨论】:

  • +1 用于自我标记作业,到目前为止我还没有看到经常发生的事情。

标签: algorithm assembly division


【解决方案1】:

不知道你被限制在什么确切的操作,但我想你会用伪代码做长除法,像这样:

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)

要实际计算商(或至少是其绝对值),最后一部分应该是这样的:

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

这些都没有经过测试,而且可能充满了错误。

【讨论】:

  • 这个神秘的“呕吐”操作可能是什么?
  • 当然是自定义操作。您的指示是否说明了除数为 0 时要做什么?
  • 谢谢!我将此代码更改为 Python,它似乎可以工作。
【解决方案2】:

我可以想到两种可能的方法。因为这是家庭作业,所以我将仅提及它们,如果可行以及如何实施它们,请让您工作:

  1. A/B = 2^(log2(A)-log2(b)):如果你能得到数值的对数,你就可以近似除法。

  2. 二进制长除法:您在进行除法之前就学会了如何进行十进制长除法,对吧?所以教你的计算机做二进制长除法(实际上二进制应该更容易)。

(编辑:更正 #1.,对数除法方程)

【讨论】:

  • 嗯,那不是A/B = 10**(log(A)-log(B))吗?
  • 您已经建议了获得商的方法,OP 要求的是余数。此外,即使使用对数进行除法的一半近似也需要浮点精度,这对于找到整数余数来说是多余的。 @jmucchiello:你是对的,但考虑到这种情况,基数更有可能是 2 而不是 10。
  • [+1 为自己标记作业] 一定要回顾一下自己如何在纸和铅笔中进行多位数除法(哦,震惊死树!)然后在你的程序中实现同样的方法。附言。如果您也对平方根执行相同的操作,则可以加分;)
  • jmucchiello:嗯,是的。虽然基数 10 并不意味着暗示......我会更正。
  • sykora:是的,这是作业,所以我没有给出完整的答案(其他人也不应该)。我认为我们大多数人都知道如何在这里从商到余数,而且这很容易。重新,对数近似:可以从没有 FP 的整数中提取足够接近近似值的二进制对数(通过左移)。我知道是因为 35 年前我用半加器电路做到了。你会注意到这里的其他人也已经知道了这一点。
【解决方案3】:

这并不能直接回答您的问题,但仍然是一个有趣的案例。如果数字是由 2 的幂模取模,则操作可以执行为

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

其中使用单个 AND 操作,通常是一个或两个循环操作。

更多信息At Wikipedia

【讨论】:

    【解决方案4】:

    似乎将 b 减去(或如果 a 为负数则添加)直到您达到或超过 0 将是一个简单的实现,尽管几乎可以肯定不是最有效的。

    【讨论】:

    • 我同意。这称为重复减法除法。
    【解决方案5】:

    Jweede,我不知道如何解决您的问题,但我发现了一个看似相关的帖子 here

    【讨论】:

    • 这是对 mod op 优化的一个很好的总结。如果我必须为类编写编译器,我肯定会把这个网站收起来。谢谢!!
    【解决方案6】:

    A/B=Q,因此 A=B*Q。我们知道 A 和 B,我们想要 Q。

    我的想法是: 二分搜索 Q。从 Q=0 & Q=1 开始,可能是基本情况。继续加倍直到 B * Q > A,然后你有两个界限(Q 和 Q/2),所以在这两个界限之间找到正确的 Q。 O(log(A/B)),但实现起来有点棘手:

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

    此外,您还可以遍历位,将每个位设置为 1。如果 B * Q LSB。 (但是,您需要能够检测到 B*Q 会溢出。

    【讨论】:

      【解决方案7】:

      谢谢大家的建议!

      我开始使用简单的除法重复减法算法来实现这一点。但正如 ysth 所指出的,有一种更简单的方法。这是第一个算法:

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

      这非常类似于:

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

      【讨论】:

      • 是的,有一种更有效的方法;看我的回答。它可能看起来要多得多的代码,但每个循环最多只能执行 32 或 64 次,而您的循环可能会执行无数次。
      • 我当然不想循环无数次。 :-(
      【解决方案8】:

      mod 可以逐位计算:

      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;
      

      这给了你余数,q 将是商。 如果你有 bsrl 指令,你可以为 i 设置一个更好的上限,因为你只能从最高位开始。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-15
        • 2014-07-02
        • 2019-06-11
        • 2017-01-21
        • 2019-09-04
        • 1970-01-01
        相关资源
        最近更新 更多