【问题标题】:How to add two numbers without using ++ or + or another arithmetic operator如何在不使用 ++ 或 + 或其他算术运算符的情况下将两个数字相加
【发布时间】:2010-11-12 02:30:46
【问题描述】:

如何在不使用 ++ 或 + 或任何其他算术运算符的情况下将两个数字相加?

这是很久以前在一些校园采访中提出的问题。不管怎样,今天有人问了一个关于一些位操作的问题,并在回答中提到了一个漂亮的 quide Stanford bit twiddling。我花了一些时间研究它,并认为这个问题实际上可能有答案。我不知道,我找不到一个。有答案吗?

【问题讨论】:

    标签: c++ c algorithm bit-manipulation


    【解决方案1】:

    您可以将adder circuit 转换为算法。他们只做按位运算 =)

    【讨论】:

      【解决方案2】:

      所有算术运算都分解为按位运算,以便在电子设备中使用 NAND、AND、OR 等门实现。

      Adder composition can be seen here.

      【讨论】:

      • 在纳米电子学中仅使用 NAND ~(a&b) 或仅 NOR ~(a|b) 操作。这是因为任何布尔函数都可以通过仅使用 NAND 或仅 NOR 门的组合来实现。生产数百万个 NAND 的同质阵列也便宜得多。它们的互连定义了整个集成电路的逻辑。内存寄存器、加法器、乘法器等 - 全部由 NAND 逻辑元件产生。
      【解决方案3】:

      对于无符号数,使用与第一堂课相同的加法算法,但以 2 为底而不是 10。例如 3+2(以 10 为底),即以 2 为底的 11+10:

         1         ‹--- carry bit
         0 1 1     ‹--- first operand (3)
       + 0 1 0     ‹--- second operand (2)
       -------
         1 0 1     ‹--- total sum (calculated in three steps)
      

      【讨论】:

        【解决方案4】:

        如果您喜欢喜剧,总有一种非常糟糕的方法可以将两个(相对较小的)无符号整数相加。代码中的任何地方都没有算术运算符。

        在 C# 中:

        static uint JokeAdder(uint a, uint b)
        {
            string result = string.Format(string.Format("{{0,{0}}}{{1,{1}}}", a, b), null, null);
            return result.Length;
        }
        

        在 C 中,使用 stdio(在 Microsoft 编译器上将 snprintf 替换为 _snprintf):

        #include <stdio.h>
        unsigned int JokeAdder(unsigned int a, unsigned int b)
        {
            return snprintf(NULL, 0, "%*.*s%*.*s", a, a, "", b, b, "");
        }
        

        【讨论】:

        • 我刚在MSDN上查了_snprintf,它说如果格式化字符串的长度大于“count”(“count”是第二个参数),那么返回值为负。如果第一个参数为 NULL,它的行为会有所不同吗?
        • 我实际上是使用 Visual C++ 编写了这个函数。有用。 :) 问题是计数为零。这使得 snprintf 的行为类似于 _scprintf,返回所需的缓冲区大小,而不是尝试将数据复制到太小的缓冲区中。
        • 当然,如果您使用 _snprintf,它表示它可能不安全,您应该在指定最大格式化长度和缓冲区大小的地方使用 _snprintf_s。很可能,_snprintf_s 只是一个包装器,而不是调用 _snprintf 并使用两个大小中的较小者。
        【解决方案5】:

        这是我前段时间为了好玩而写的东西。它使用two's complement 表示,并使用带有进位位的重复移位来实现加法,主要是在加法方面实现其他运算符。

        #include <stdlib.h> /* atoi() */
        #include <stdio.h>  /* (f)printf */
        #include <assert.h> /* assert() */
        
        int add(int x, int y) {
            int carry = 0;
            int result = 0;
            int i;
        
            for(i = 0; i < 32; ++i) {
                int a = (x >> i) & 1;
                int b = (y >> i) & 1;
                result |= ((a ^ b) ^ carry) << i;
                carry = (a & b) | (b & carry) | (carry & a);
            }
        
            return result;
        }
        
        int negate(int x) {
            return add(~x, 1);
        }
        
        int subtract(int x, int y) {
            return add(x, negate(y));
        }
        
        int is_even(int n) {
            return !(n & 1);
        }
        
        int divide_by_two(int n) {
            return n >> 1;
        }
        
        int multiply_by_two(int n) {
            return n << 1;
        }
        
        int multiply(int x, int y) {
            int result = 0;
        
            if(x < 0 && y < 0) {
                return multiply(negate(x), negate(y));
            }
        
            if(x >= 0 && y < 0) {
                return multiply(y, x);
            }
        
            while(y > 0) {
                if(is_even(y)) {
                    x = multiply_by_two(x);
                    y = divide_by_two(y);
                } else {
                    result = add(result, x);
                    y = add(y, -1);
                }
            }
        
            return result;
        }
        
        int main(int argc, char **argv) {
            int from = -100, to = 100;
            int i, j;
        
            for(i = from; i <= to; ++i) {
                assert(0 - i == negate(i));
                assert(((i % 2) == 0) == is_even(i));
                assert(i * 2 == multiply_by_two(i));
                if(is_even(i)) {
                    assert(i / 2 == divide_by_two(i));
                }
            }
        
            for(i = from; i <= to; ++i) {
                for(j = from; j <= to; ++j) {
                    assert(i + j == add(i, j));
                    assert(i - j == subtract(i, j));
                    assert(i * j == multiply(i, j));
                }
            }
        
            return 0;
        }
        

        【讨论】:

        • +1 哇,这家伙太疯狂了!我在做数字设计的时候就想过这种事情,但从来没有真正为它写过代码!
        • 当然你需要展开循环来摆脱递增的索引:)
        • @Eugene:哈哈,说得好。显然这不是一个 100% 的纯实现......可能有一些聪明的方法可以在不增加索引的情况下重写它。此外,在multiply 中,我使用了一些比较运算符,这可能被认为是作弊,尽管由于我基本上只是在检查负值,因此将它们转换为 bit-frobbing 是非常微不足道的。
        • 无需展开。只需将 for 循环转换为基于在适当时刻产生零的位移的 while 循环。
        • 为了便携性,你应该从int切换到unsigned
        【解决方案6】:

        以下方法可行。

        x - (-y)
        

        【讨论】:

        • 聪明,但听起来它在“或任何其他算术运算符”部分失败了。
        • 很搞笑
        【解决方案7】:

        或者,与 Jason 的按位方法不同,您可以并行计算许多位 - 这应该在处理大量数字时运行得更快。在每一步中,找出进位部分和总和部分。您尝试将进位添加到总和中,这可能会再次导致进位 - 因此是循环。

        >>> def add(a, b):
            while a != 0:
                #      v carry portion| v sum portion
                a, b = ((a & b) << 1),  (a ^ b)
                print b, a
            return b
        

        当您将 1 和 3 相加时,两个数字都设置了 1 位,因此 1+1 的总和进行了。下一步将 2 加到 2 中,得到正确的总和 4。这会导致退出

        >>> add(1,3)
        2 2
        4 0
        4
        

        或者更复杂的例子

        >>> add(45, 291)
        66 270
        4 332
        8 328
        16 320
        336
        

        编辑: 为了让它在有符号的数字上轻松工作,您需要在 a 和 b 上引入一个上限

        >>> def add(a, b):
            while a != 0:
                #      v carry portion| v sum portion
                a, b = ((a & b) << 1),  (a ^ b)
                a &= 0xFFFFFFFF
                b &= 0xFFFFFFFF
                print b, a
            return b
        

        试一试

        add(-1, 1)
        

        看到一个位在整个范围内向上进位并在 32 次迭代中溢出

        4294967294 2
        4294967292 4
        4294967288 8
        ...
        4294901760 65536
        ...
        2147483648 2147483648
        0 0
        0L
        

        【讨论】:

        • +1 比我的更优雅、更高效。也消除了对递增索引变量的需要以及知道系统上“int”类型有多宽的需要。
        • +1 假设这确实有效。但它看起来很漂亮:')
        • @ForceMagic - 别傻了!当然 + 比我这里的逻辑要快。 + 使用内置在 CPU 中的硬件,并在我的 Python 循环中使用一条 CPU 指令而不是一大堆指令。
        • @ForceMagic 如果这个版本在某种程度上比 operator+ 快,那么编译器编写者只会根据这个来实现 operator+
        • 如果我将两个负数相加,则答案不正确。它给了我一个很大的正整数。
        【解决方案8】:

        嗯,用布尔运算符实现等价的操作非常简单:您可以进行逐位求和(即 XOR)和进位(即 AND)。像这样:

        int sum(int value1, int value2)
        {
            int result = 0;
            int carry = 0;
            for (int mask = 1; mask != 0; mask <<= 1)
            {
                int bit1 = value1 & mask;
                int bit2 = value2 & mask;
                result |= mask & (carry ^ bit1 ^ bit2);
                carry = ((bit1 & bit2) | (bit1 & carry) | (bit2 & carry)) << 1;
            }
            return result;
        }
        

        【讨论】:

          【解决方案9】:
          int Add(int a, int b)
          {
              while (b)
              {
                  int carry = a & b;
                  a = a ^ b;
                  b = carry << 1;
              }
              return a;
          }
          

          【讨论】:

          • 令人惊讶的是,它可以在不到 5 秒的时间内移植到 JavaScript。函数 Add(a, b) { while (b) { var carry = a & b; a = a ^ b; b = 进位
          • 你能解释一下你做了什么吗?如果可能,一步一步来。谢谢!
          【解决方案10】:

          您已经得到了一些操纵答案。这是不同的东西。

          在 C 中,arr[ind] == *(arr + ind)。这让我们可以做一些稍微令人困惑(但合法)的事情,比如int arr = { 3, 1, 4, 5 }; int val = 0[arr];

          因此我们可以定义一个自定义的加法函数(无需显式使用算术运算符):

          unsigned int add(unsigned int const a, unsigned int const b)
          {
              /* this works b/c sizeof(char) == 1, by definition */
              char * const aPtr = (char *)a;
              return (int) &(aPtr[b]);
          }
          

          另外,如果我们想避免这个技巧,并且如果通过算术运算符它们包括|&amp;^(因此不允许直接位操作),我们可以通过查找表来实现:

          typedef unsigned char byte;
          
          const byte lut_add_mod_256[256][256] = { 
            { 0, 1, 2, /*...*/, 255 },
            { 1, 2, /*...*/, 255, 0 },
            { 2, /*...*/, 255, 0, 1 },
            /*...*/
            { 254, 255, 0, 1, /*...*/, 253 },
            { 255, 0, 1, /*...*/, 253, 254 },
          }; 
          
          const byte lut_add_carry_256[256][256] = {
            { 0, 0, 0, /*...*/, 0 },
            { 0, 0, /*...*/, 0, 1 },
            { 0, /*...*/, 0, 1, 1 },
            /*...*/
            { 0, 0, 1, /*...*/, 1 },
            { 0, 1, 1, /*...*/, 1 },
          };
          
          void add_byte(byte const a, byte const b, byte * const sum, byte * const carry)
          {
            *sum = lut_add_mod_256[a][b];
            *carry = lut_add_carry_256[a][b];
          }
          
          unsigned int add(unsigned int a, unsigned int b)
          {
            unsigned int sum;
            unsigned int carry;
            byte * const aBytes = (byte *) &a;
            byte * const bBytes = (byte *) &b;
            byte * const sumBytes = (byte *) &sum;
            byte * const carryBytes = (byte *) &carry;
          
            byte const test[4] = { 0x12, 0x34, 0x56, 0x78 };
            byte BYTE_0, BYTE_1, BYTE_2, BYTE_3;
          
            /* figure out endian-ness */
            if (0x12345678 == *(unsigned int *)test)
            {
              BYTE_0 = 3;
              BYTE_1 = 2;
              BYTE_2 = 1;
              BYTE_3 = 0;
            }
            else 
            {
              BYTE_0 = 0;
              BYTE_1 = 1;
              BYTE_2 = 2;
              BYTE_3 = 3;
            }
          
          
            /* assume 4 bytes to the unsigned int */
            add_byte(aBytes[BYTE_0], bBytes[BYTE_0], &sumBytes[BYTE_0], &carryBytes[BYTE_0]);
          
            add_byte(aBytes[BYTE_1], bBytes[BYTE_1], &sumBytes[BYTE_1], &carryBytes[BYTE_1]);
            if (carryBytes[BYTE_0] == 1)
            {
              if (sumBytes[BYTE_1] == 255)
              {
                sumBytes[BYTE_1] = 0;
                carryBytes[BYTE_1] = 1;
              }
              else
              {
                add_byte(sumBytes[BYTE_1], 1, &sumBytes[BYTE_1], &carryBytes[BYTE_0]);
              }
            }
          
            add_byte(aBytes[BYTE_2], bBytes[BYTE_2], &sumBytes[BYTE_2], &carryBytes[BYTE_2]);
            if (carryBytes[BYTE_1] == 1)
            {
              if (sumBytes[BYTE_2] == 255)
              {
                sumBytes[BYTE_2] = 0;
                carryBytes[BYTE_2] = 1;
              }
              else
              {
                add_byte(sumBytes[BYTE_2], 1, &sumBytes[BYTE_2], &carryBytes[BYTE_1]);
              }
            }
          
            add_byte(aBytes[BYTE_3], bBytes[BYTE_3], &sumBytes[BYTE_3], &carryBytes[BYTE_3]);
            if (carryBytes[BYTE_2] == 1)
            {
              if (sumBytes[BYTE_3] == 255)
              {
                sumBytes[BYTE_3] = 0;
                carryBytes[BYTE_3] = 1;
              }
              else
              {
                add_byte(sumBytes[BYTE_3], 1, &sumBytes[BYTE_3], &carryBytes[BYTE_2]);
              }
            }
          
            return sum;
          }
          

          【讨论】:

          • 很高兴有一些不同的答案! return (int) &amp;(aPtr[b]); 有点作弊,因为它编译为与 a + b 完全相同的添加操作。查找表当然很有趣。值得注意的是,bBytes[BYTE_3](比如说)也是一个加法,因为它添加了 &bBytes + BYTE_3 以获取要读取的内存地址。
          【解决方案11】:
          short int ripple_adder(short int a, short int b)
          {
              short int i, c, s, ai, bi;
          
              c = s = 0;
          
              for (i=0; i<16; i++)
              {
                  ai = a & 1;
                  bi = b & 1;
          
                  s |= (((ai ^ bi)^c) << i);
                  c = (ai & bi) | (c & (ai ^ bi));
          
                  a >>= 1;
                  b >>= 1;
              }
              s |= (c << i);
              return s;
          }
          

          【讨论】:

            【解决方案12】:
            #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;
            }
            

            【讨论】:

              【解决方案13】:
              ## to add or subtract without using '+' and '-' ## 
              #include<stdio.h>
              #include<conio.h>
              #include<process.h>
              
              void main()
              {
                  int sub,a,b,carry,temp,c,d;
              
                  clrscr();
              
                  printf("enter a and b:");
                  scanf("%d%d",&a,&b);
              
                  c=a;
                  d=b;
                  while(b)
                  {
                      carry=a&b;
                      a=a^b;
                      b=carry<<1;
                  }
                  printf("add(%d,%d):%d\n",c,d,a);
              
                  temp=~d+1;  //take 2's complement of b and add it with a
                  sub=c+temp;
                  printf("diff(%d,%d):%d\n",c,d,temp);
                  getch();
              }
              

              【讨论】:

                【解决方案14】:

                不使用+,*运算符实现加、乘的代码; 对于减法,将数字的 1 的补码 +1 传递给 add 函数

                #include<stdio.h>
                
                unsigned int add(unsigned int x,unsigned int y)
                {
                         int carry=0;
                    while (y != 0)
                    {
                
                        carry = x & y;  
                        x = x ^ y; 
                        y = carry << 1;
                    }
                    return x;
                }
                int multiply(int a,int b)
                {
                    int res=0;
                    int i=0;
                    int large= a>b ? a :b ;
                    int small= a<b ? a :b ;
                    for(i=0;i<small;i++)
                    {
                           res = add(large,res);                    
                    }
                    return res;
                }
                int main()
                {
                    printf("Sum :: %u,Multiply is :: %d",add(7,15),multiply(111,111));
                    return 0;
                }
                

                【讨论】:

                  【解决方案15】:

                  这可以递归完成:

                  int add_without_arithm_recursively(int a, int b)
                  {
                      if (b == 0) 
                          return a;
                  
                      int sum = a ^ b; // add without carrying
                      int carry = (a & b) << 1; // carry, but don’t add
                      return add_without_arithm_recursively(sum, carry); // recurse
                  }
                  

                  或迭代:

                  int add_without_arithm_iteratively(int a, int b)
                  {
                      int sum, carry;
                  
                      do 
                      {
                          sum = a ^ b; // add without carrying
                          carry = (a & b) << 1; // carry, but don’t add
                  
                          a = sum;
                          b = carry;
                      } while (b != 0);
                  
                      return a;
                  }
                  

                  【讨论】:

                    【解决方案16】:

                    问题询问如何将两个数字相加,所以我不明白为什么所有解决方案都提供两个整数相加?如果这两个数字是浮点数,即2.3 + 1.8,它们是否也不被视为数字?要么问题需要修改,要么答案需要修改。

                    对于浮点数,我认为应该将数字分解为其组成部分,即2.3 = 2 + 0.3,然后应将0.3 乘以其指数因子转换为整数表示,即0.3 = 3 * 10^-1 对其他数字执行相同操作,然后使用作为上述处理情况的解决方案之一的位移方法添加整数段以结转到单位数字位置,即2.7 + 3.3 = 6.0 = 2+3+0.7+0.3 = 2 + 3 + 7x10^-1 + 3x10^-1 = 2 + 3 + 10^10^-1(这可以作为两个单独的加法处理2+3=5然后5+1=6

                    【讨论】:

                    • 在加法方面,浮点(按设计)的完成方式与整数相同。浮点数在内部存储,类似于带有二进制尾数的整数。例如,如果您在以 10 为基数工作,则 2.56 的存储可能看起来像 .256*10^1,但在二进制中您会得到 .101101x2^5 或其他东西。关键是,一旦你移动了位,所以尾数是相同的,加法就和整数一样。当我们达到那个相似点并且可能不会提及它时,我们程序员将其视为“已解决” :)
                    【解决方案17】:
                    int add_without_arithmatic(int a, int b)
                    {
                        int sum;
                        char *p;
                        p = (char *)a;
                        sum = (int)&p[b];
                        printf("\nSum : %d",sum);
                    }
                    

                    【讨论】:

                    • 请解释一下这个函数的作用?
                    • 可爱,但技术上未定义的行为(你正在形成一个越界指针)。
                    【解决方案18】:

                    这是一个紧凑的 C 解决方案。有时递归比循环更具可读性。

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

                    【讨论】:

                      【解决方案19】:

                      根据上面给出的答案,可以用单行代码完成:

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

                      【讨论】:

                      • 这并没有真正为if (b == 0) return a; return add(a ^ b, (a &amp; b) &lt;&lt; 1); 的现有答案添加任何内容。它只是将if 替换为?:
                      • @melpomene 是的,只需将 2 行改为 1 行
                      【解决方案20】:

                      您可以使用双否定来添加两个整数,例如:

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

                      【讨论】:

                      • 这怎么遵守without using [any] arithmetic operator?
                      • 在不使用任何运算符的情况下,可以将两个整数相加。在这个页面上查看我刚刚发布了新的答案,你可以通过三种方式做到这一点。
                      【解决方案21】:

                      在不使用任何运算符的情况下将两个整数相加可以通过以下不同方式完成:

                      int sum_of_2 (int a, int b){
                         int sum=0, carry=sum;
                         sum =a^b;
                         carry = (a&b)<<1;
                         return (b==0)? a: sum_of_2(sum, carry);
                      }
                      // Or you can just do it in one line as follows:
                      int sum_of_2 (int a, int b){
                         return (b==0)? a: sum_of_2(a^b, (a&b)<<1);
                      }
                      // OR you can use the while loop instead of recursion function as follows
                      int sum_of_2 (int a, int b){
                          if(b==0){
                             return a;
                         }
                         while(b!=0){
                           int sum = a^b;
                           int carry = (a&b)<<1;
                           a= sum;
                           b=carry;
                        }
                        return a;
                      }
                      

                      【讨论】:

                      猜你喜欢
                      • 2011-04-08
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多