【问题标题】:What is the best way to add two numbers without using the + operator?在不使用 + 运算符的情况下添加两个数字的最佳方法是什么?
【发布时间】:2010-09-26 19:16:10
【问题描述】:

我和一个朋友来回玩脑筋急转弯,我不知道如何解决这个问题。我的假设是使用一些位运算符是可能的,但不确定。

【问题讨论】:

  • 您可以通过一个循环来查看每个位 >> 直到值为 0(然后您将处理所有位)。首先将其转换为未签名的。我会中奖吗?
  • 谢谢。你的回报是知道你帮助了一位陷入困境的女士。
  • 如果可以使用NO操作符,那么不排除位操作符吗?或者只是 +-*/ ?
  • 算盘就可以做到这一点,而且不耗电!
  • 我将使用 std::plus()(a, b)

标签: low-level addition


【解决方案1】:
This can be done using Half Adder.
Half Adder is method to find sum of numbers with single bit.
A   B    SUM   CARRY   A & B    A ^ B
0   0    0     0        0         0
0   1    1     0        0         1
1   0    1     0        0         1
1   1    0     1        0         0

We can observe here that SUM = A ^ B and CARRY = A & B
We know CARRY is always added at 1 left position from where it was 
generated.
so now add ( CARRY << 1 ) in SUM, and repeat this process until we get 
Carry 0.

int Addition( int a, int b)
{
 if(B==0)
    return A;
 Addition( A ^ B, (A & B) <<1 )
}

let's add 7 (0111) and 3 (0011) answer will be 10 (1010)
  1. A = 0100 和 B = 0110
  2. A = 0010 和 B = 1000
  3. A = 1010 和 B = 0000 最终答案是 A。

【讨论】:

    【解决方案2】:

    这是 C++ 中的解决方案,你可以在我的 github 上找到它:https://github.com/CrispenGari/Add-Without-Integers-without-operators/blob/master/main.cpp

    int add(int a, int b){
       while(b!=0){
          int sum = a^b; // add without carrying
          int carry = (a&b)<<1; // carrying without adding
          a= sum;
          b= carry;
        }
       return a;
     }
     // the function can be writen as follows :
     int add(int a, int b){
         if(b==0){
            return a; // any number plus 0 = that number simple!
        }
        int sum = a ^ b;// adding without carrying;
        int carry = (a & b)<<1; // carry, without adding
        return add(sum, carry);
      }
    

    【讨论】:

      【解决方案3】:

      基于 Go 的解决方案

      func add(a int, b int) int {
      
      for {
          carry := (a & b) << 1
          a = a ^ b
          b = carry 
          if b == 0 {
              break
          }
      }
      
      return a 
      
      }
      

      同样的解决方案可以在 Python 中实现如下,但是在 Python 中数字表示存在一些问题,Python 有超过 32 位的整数。所以我们将使用掩码来获取最后 32 位。

      例如:如果我们不使用掩码,我们将不会得到数字 (-1,1) 的结果

      def add(a,b):   
          mask = 0xffffffff
      
          while b & mask:
              carry = a & b
              a = a ^ b
              b = carry << 1
      
          return (a & mask)
      

      【讨论】:

      • 只使用return a&amp;mask 会更简单。检查您是否可能不需要使代码复杂化,&amp; 很便宜。
      【解决方案4】:

      这是一个可移植的单行三元递归解决方案。

      int add(int x, int y) {
          return y == 0 ? x : add(x ^ y, (x & y) << 1);
      }
      

      【讨论】:

        【解决方案5】:

        在python中使用位运算符:

        def sum_no_arithmetic_operators(x,y):
            while True:
                carry = x & y
                x = x ^ y
                y = carry << 1
                if y == 0:
                    break
            return x
        

        【讨论】:

        【解决方案6】:

        如果输入的符号相反,则投票最多的答案将不起作用。然而,以下将。我在一个地方作弊,但只是为了保持代码有点干净。欢迎任何改进建议

        def add(x, y):
        if (x >= 0 and y >= 0) or (x < 0 and y < 0):
            return _add(x, y)
        else:
            return __add(x, y)
        
        
        def _add(x, y):
        if y == 0:
            return x
        else:
            return _add((x ^ y), ((x & y) << 1))
        
        
        def __add(x, y):
        if x < 0 < y:
            x = _add(~x, 1)
            if x > y:
                diff = -sub(x, y)
            else:
                diff = sub(y, x)
            return diff
        elif y < 0 < x:
            y = _add(~y, 1)
            if y > x:
                diff = -sub(y, x)
            else:
                diff = sub(y, x)
            return diff
        else:
            raise ValueError("Invalid Input")
        
        
        def sub(x, y):
        if y > x:
            raise ValueError('y must be less than x')
        while y > 0:
            b = ~x & y
            x ^= y
            y = b << 1
        return x
        

        【讨论】:

        • 如果输入符号相反,投票最多的答案将不起作用 - 它在整数类型为固定宽度的 C 中有效。 (至少假设 2 的补码。)我用负数尝试过:godbolt.org/z/Lhyh4Y。也许你的意思是它不会在 Python 中工作
        【解决方案7】:

        您可以使用位移位和 AND 操作来实现。

        #include <stdio.h>
        
        int main()
        {
            unsigned int x = 3, y = 1, sum, carry;
            sum = x ^ y; // Ex - OR x and y
            carry = x & y; // AND x and y
            while (carry != 0) {
                carry = carry << 1; // left shift the carry
                x = sum; // initialize x as sum
                y = carry; // initialize y as carry
                sum = x ^ y; // sum is calculated
                carry = x & y; /* carry is calculated, the loop condition is
                                evaluated and the process is repeated until
                                carry is equal to 0.
                                */
            }
            printf("%d\n", sum); // the program will print 4
            return 0;
        }
        

        【讨论】:

        • do{}while() 循环在这里实际上更容易,就像当前接受的答案一样。
        【解决方案8】:

        这是我在 Python 上的实现。当我们知道字节数(或位数)时,它就可以很好地工作。

        def summ(a, b):
            #for 4 bytes(or 4*8 bits)
            max_num = 0xFFFFFFFF
            while a != 0:
                a, b = ((a & b) << 1),  (a ^ b)
                if a > max_num:
                    b = (b&max_num) 
                    break
            return b
        

        【讨论】:

          【解决方案9】:

          我在编码面试中将此视为问题 18.1。 我的python解决方案:

          def foo(a, b):
          """iterate through a and b, count iteration via a list, check len"""
              x = []
              for i in range(a):
                      x.append(a)
              for i in range(b):
                      x.append(b)
              print len(x)
          

          此方法使用迭代,因此时间复杂度不是最优的。 我相信最好的方法是使用按位运算在较低级别上工作。

          【讨论】:

            【解决方案10】:

            以与我们在纸上进行二进制加法相同的方式实现。

            int add(int x, int y)
            {
                int t1_set, t2_set;
                int carry = 0;
                int result = 0;
                int mask = 0x1;
            
                while (mask != 0) {
                    t1_set = x & mask;
                    t2_set = y & mask;
                    if (carry) {
                       if (!t1_set && !t2_set) {
                           carry = 0;
                           result |= mask;
                       } else if (t1_set && t2_set) {
                           result |= mask;
                       }
                    } else {
                       if ((t1_set && !t2_set) || (!t1_set && t2_set)) {
                            result |= mask;
                       } else if (t1_set && t2_set) {
                            carry = 1;
                       }
                    }
                    mask <<= 1;
                }
                return (result);
            }
            

            提高速度如下::

            int add_better (int x, int y)
            {
              int b1_set, b2_set;
              int mask = 0x1;
              int result = 0;
              int carry = 0;
            
              while (mask != 0) {
                  b1_set = x & mask ? 1 : 0;
                  b2_set = y & mask ? 1 : 0;
                  if ( (b1_set ^ b2_set) ^ carry)
                      result |= mask;
                  carry = (b1_set &  b2_set) | (b1_set & carry) | (b2_set & carry);
                  mask <<= 1;
              }
              return (result);
            }
            

            【讨论】:

              【解决方案11】:

              注意,这适用于称为ripple-carry adder 的加法器,它可以工作,但性能不佳。大多数内置在硬件中的二进制加法器都是一种快速加法器,例如carry-look-ahead adder

              如果将 carry_in 设置为 0,我的波纹进位加法器适用于无符号整数和 2 的补码整数,如果将 carry_in 设置为 1,则适用于 1 的补码整数。我还添加了标志以显示加法时的下溢或上溢。

              #define BIT_LEN 32
              #define ADD_OK 0
              #define ADD_UNDERFLOW 1
              #define ADD_OVERFLOW 2
              
              int ripple_add(int a, int b, char carry_in, char* flags) {
                  int result = 0;
                  int current_bit_position = 0;
                  char a_bit = 0, b_bit = 0, result_bit = 0;
              
                  while ((a || b) && current_bit_position < BIT_LEN) {
                      a_bit = a & 1;
                      b_bit = b & 1;
                      result_bit = (a_bit ^ b_bit ^ carry_in);
                      result |= result_bit << current_bit_position++;
                      carry_in = (a_bit & b_bit) | (a_bit & carry_in) | (b_bit & carry_in);
                      a >>= 1;
                      b >>= 1;
                  }
              
                  if (current_bit_position < BIT_LEN) {
                      *flags = ADD_OK;
                  }
                  else if (a_bit & b_bit & ~result_bit) {
                      *flags = ADD_UNDERFLOW;
                  }
                  else if (~a_bit & ~b_bit & result_bit) {
                      *flags = ADD_OVERFLOW;
                  }
                  else {
                      *flags = ADD_OK;
                  }
              
                  return result;
              }
              

              【讨论】:

              • 不幸的是,增量运算符 (current_bit_position++) 需要加法。挑剔,我知道。
              • @pomeranian.myopenid.com 是的,在这种情况下确实如此。在硬件中,每个位都有单独的逻辑门,并且不使用循环。如果要展开此循环,则可以在不使用 ++ 运算符的情况下使用它。
              • @Lara:是的,展开。对于 32 位,它将是 while 循环内代码的 32 个副本。这将提供一个很好的硬件伪代码和一个奖励点:它甚至是可执行的!编程硬件遵循与编程软件不同的规则,因此一些最佳实践不适用于此处...
              【解决方案12】:

              在 C 中,使用位运算符:

              #include<stdio.h>
              
              int add(int x, int y) {
                  int a, b;
                  do {
                      a = x & y;
                      b = x ^ y;
                      x = a << 1;
                      y = b;
                  } while (a);
                  return b;
              }
              
              
              int main( void ){
                  printf( "2 + 3 = %d", add(2,3));
                  return 0;
              }
              

              XOR (x ^ y) 是没有进位的加法。 (x &amp; y) 是每个位的进位。 (x &amp; y) &lt;&lt; 1 是每个位的进位。

              循环不断添加进位,直到所有位的进位为零。

              【讨论】:

              • 谢谢。我不敢问,但减法是否同样有效?我读到我可以添加两个补码。但是当我尝试减去 6-3 并使用二进制补码将其变为 6+(-3) 时,我在上述算法中得到了一个无限循环。
              • add(6, -3) 应该可以工作,你可以在这里玩代码:codepad.org/iWSRSsUn
              • 左移负值是未定义的行为,它将在许多处理器上按预期工作,但不能保证,您应该在回答中指出这一点。另外,你可以在你的 printf 语句中添加一个 \n 吗?除此之外,很好的答案。
              • 我尝试将您的算法转换为 Python (codepad.org/pb8IuLnY),并且在传入负数(即减法)时遇到无限循环。 Python 的运算符与 C 有什么不同吗?
              • @pomeranian.myopenid.com,这很可能是由于 Python 中处理左移运算符的方式。它没有达到整数位的上限,而是将最高位设置为负数,而是变成正长整数。
              【解决方案13】:

              作弊。你可以否定这个数字并从第一个中减去它:)

              如果做不到这一点,请查看二进制加法器的工作原理。 :)

              编辑:啊,我发布后看到你的评论。

              二进制加法的详细信息是here

              【讨论】:

              • 二进制添加的 URL 已损坏。
              • 链接已损坏,此答案的其余部分无效;它应该被删除。
              • 链接已更正,答案相关的是原始问题上的cmets的上下文。
              【解决方案14】:

              使用位运算符的 Java 解决方案:

              // Recursive solution
              public static int addR(int x, int y) {
              
                  if (y == 0) return x;
                  int sum = x ^ y; //SUM of two integer is X XOR Y
                  int carry = (x & y) << 1;  //CARRY of two integer is X AND Y
                  return addR(sum, carry);
              }
              
              //Iterative solution
              public static int addI(int x, int y) {
              
                  while (y != 0) {
                      int carry = (x & y); //CARRY is AND of two bits
                      x = x ^ y; //SUM of two bits is X XOR Y
                      y = carry << 1; //shifts carry to 1 bit to calculate sum
                  }
                  return x;
              }
              

              【讨论】:

              • 从两者中删除 public static 使其也可以在 C 中工作。 +1
              • 这正是CMS's answer (当前接受的),但具有有意义的变量名称,以及用内联 cmets 而不是文本中的解释(CMS 的答案多年来一直缺失,但我补充说它于 2016 年 7 月发布。)尽管如此,仍因清晰准确地解释它而受到支持。
              • 其实说xor是add-without-carry比较好。递归版本中的第一条注释说它是两个整数之和,这是错误的。
              • @PeterCordes CMS 的答案包括一个 main 方法并且是有效的 C 代码。我在这里添加的只是有效的 Java 方法。此代码在我的本地机器上进行了测试,而不是直接从其他来源复制粘贴。不过感谢您的 cmets。
              【解决方案15】:

              我自己正在用 C# 解决这个问题,但无法通过所有测试用例。然后我遇到了this

              这是 C# 6 中的一个实现:

              public int Sum(int a, int b) => b != 0 ? Sum(a ^ b, (a & b) << 1) : a;
              

              【讨论】:

              • 这是与 CMS 接受的答案相同的算法。
              • 我也是这么想的,但是那个答案并没有通过我所有的测试用例。所以我提供了最终用不同的编程语言为我工作的东西。有时人们在发布后很久就会遇到问题,并且与原始发布者的情况略有不同。我希望能帮助和我处境相似的人。对不起,如果我冒犯了你,如果你觉得有必要,也可以随时编辑我的答案。
              • 我没仔细看;您的算法与 CMS 的有何不同?您的递归结束检查略有不同。哦,CMS的功能应该检查while(x)而不是while(a)吗?无论如何,如果接受的答案有问题,您应该将其作为评论或作为该答案文本的一部分(或两者)进行评论。无论如何,我个人并没有被冒犯,我只是不认为这个答案增加了太多价值,因为看起来相同的算法已经发布了。
              • 没有问题。它只是不会在没有增强的情况下转换为 C#。我认为关键是语言的不同。我不认为被转移的底片表现相同。事实上,移位负数不应该保证在数学意义上正确处理负数,因为这不是移位的本质。我的答案是专门针对 C# 实施者的,并且包含一个不同的解决方案的评论可能会被这个答案可以帮助的人错过。
              【解决方案16】:

              Python 代码: (1)

              add = lambda a,b : -(-a)-(-b)
              

              使用带有“-”运算符的 lambda 函数

              (2)

              add= lambda a,b : len(list(map(lambda x:x,(i for i in range(-a,b)))))
              

              【讨论】:

                【解决方案17】:

                定义“最佳”。这是一个python版本:

                len(range(x)+range(y))
                

                + 执行列表连接,而不是添加。

                【讨论】:

                • without using the + operator - 没有说without using the addition operator
                • x = list(range(a)); x.扩展(范围(b));长度(x)
                【解决方案18】:
                int add(int a, int b) {
                   const char *c=0;
                   return &(&c[a])[b];
                }
                

                【讨论】:

                • 我不太明白这个是怎么工作的,解释一下就好了!
                • @ffledgling c的地址最初是0。c[a]的地址是0 + a = a。而(&amp;c[a])[b]的地址是a + b。不错的作弊,尽管仍然隐式使用 add
                • 请注意,您需要分配一个足够大的数组以容纳最大的总和。否则,创建超出数组边界的指针是未定义的行为
                • @Nayuki 不过,这不是一个数组。
                【解决方案19】:

                CMS 的 add() 函数很漂亮。它不应该被一元否定所玷污(非按位运算,相当于使用加法:-y==(~y)+1)。所以这是一个使用相同的仅按位设计的减法函数:

                int sub(int x, int y) {
                    unsigned a, b;
                    do {
                        a = ~x & y;
                        b =  x ^ y;
                        x = b;
                        y = a << 1;
                    } while (a);
                    return b;
                }
                

                【讨论】:

                • 这并没有提供问题的答案,它要求加法,而不是减法。
                • @MD XF,我正在回答问题 user23126 asked in the comments of CMS's answer。我觉得 CMS 对此评论的回答并不令人满意,因为正如我上面解释的,一元否定等同于使用加法。无法将多行代码放在评论中,因此我将其发布为答案。另请注意,user23126 是最初的提问者——所以在某种程度上,这确实符合回答问题的条件。
                • 另外,虽然这个问题确实询问了如何在不使用 + 运算符的情况下添加两个数字,但正如其他人所说,a - (-b) 很容易实现。因此,在不使用任何算术运算符的情况下回答如何做到这一点更符合问题的精神。另外,user23126 directly stated 一个不是字面意思是 + 的运算符在进行加法时仍然不可接受,而 ++ 与否定在幕后所做的部分非常相似。
                【解决方案20】:

                没有+对吗?

                int add(int a, int b) 
                {
                   return -(-a) - (-b);
                }
                

                【讨论】:

                • 在问题 cmets 中,@pomeranian.myopenid.com 提到不能使用算术运算符。此外,最好将减法作为替代操作,将其写成 - (-b)。
                【解决方案21】:

                两个整数相加并不难;网上有很多二进制加法的例子。

                一个更具挑战性的问题是浮点数! http://pages.cs.wisc.edu/~smoler/x86text/lect.notes/arith.flpt.html有一个例子

                【讨论】:

                  【解决方案22】:

                  在汇编程序中将 ADD 实现为单个指令而不是按位操作的某种组合的原因是它很难做到。您必须担心从给定的低位到下一个高位的进位。这是机器在硬件中快速完成的事情,但即使使用 C,您也无法在软件中快速完成。

                  【讨论】:

                  • 如果您设法在 C 中编写了与硬件add 指令对所有不会导致未定义行为的输入所做的完全匹配的内容,则编译器可以使用add。对于popcnt 这样的事情,我们现在正处于这种情况,其中获得popcnt 指令的唯一纯ISO C 方法是让编译器识别一个习语并将您的循环或bithack 序列优化为popcnt(是的,编译器会这样做)。或进行轮换。 stackoverflow.com/questions/776508/….
                  • 显然,在 C 中使用 + 运算符比替代方案要好得多,但丑陋的源代码将是主要问题,而不是慢代码。嘿,或者foo = (int) &amp;((char*)x)[y] 使用数组索引语法作为+ 运算符,但即使创建一个伪造的指针也是C 中的UB。
                  【解决方案23】:

                  为什么不像第二个数字一样经常增加第一个数字?

                  【讨论】:

                  • 递增就是加1,所以原来的问题依然存在。
                  • 不是真的,INC 和 ADD 是机器语言中不同的操作码;)
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2010-10-16
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-05-02
                  相关资源
                  最近更新 更多