【发布时间】:2010-11-12 02:30:46
【问题描述】:
如何在不使用 ++ 或 + 或任何其他算术运算符的情况下将两个数字相加?
这是很久以前在一些校园采访中提出的问题。不管怎样,今天有人问了一个关于一些位操作的问题,并在回答中提到了一个漂亮的 quide Stanford bit twiddling。我花了一些时间研究它,并认为这个问题实际上可能有答案。我不知道,我找不到一个。有答案吗?
【问题讨论】:
标签: c++ c algorithm bit-manipulation
如何在不使用 ++ 或 + 或任何其他算术运算符的情况下将两个数字相加?
这是很久以前在一些校园采访中提出的问题。不管怎样,今天有人问了一个关于一些位操作的问题,并在回答中提到了一个漂亮的 quide Stanford bit twiddling。我花了一些时间研究它,并认为这个问题实际上可能有答案。我不知道,我找不到一个。有答案吗?
【问题讨论】:
标签: c++ c algorithm bit-manipulation
您可以将adder circuit 转换为算法。他们只做按位运算 =)
【讨论】:
所有算术运算都分解为按位运算,以便在电子设备中使用 NAND、AND、OR 等门实现。
【讨论】:
对于无符号数,使用与第一堂课相同的加法算法,但以 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)
【讨论】:
如果您喜欢喜剧,总有一种非常糟糕的方法可以将两个(相对较小的)无符号整数相加。代码中的任何地方都没有算术运算符。
在 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, "");
}
【讨论】:
这是我前段时间为了好玩而写的东西。它使用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;
}
【讨论】:
multiply 中,我使用了一些比较运算符,这可能被认为是作弊,尽管由于我基本上只是在检查负值,因此将它们转换为 bit-frobbing 是非常微不足道的。
int切换到unsigned。
以下方法可行。
x - (-y)
【讨论】:
或者,与 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
【讨论】:
嗯,用布尔运算符实现等价的操作非常简单:您可以进行逐位求和(即 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;
}
【讨论】:
int Add(int a, int b)
{
while (b)
{
int carry = a & b;
a = a ^ b;
b = carry << 1;
}
return a;
}
【讨论】:
您已经得到了一些操纵答案。这是不同的东西。
在 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]);
}
另外,如果我们想避免这个技巧,并且如果通过算术运算符它们包括|、& 和^(因此不允许直接位操作),我们可以通过查找表来实现:
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 *) ∑
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) &(aPtr[b]); 有点作弊,因为它编译为与 a + b 完全相同的添加操作。查找表当然很有趣。值得注意的是,bBytes[BYTE_3](比如说)也是一个加法,因为它添加了 &bBytes + BYTE_3 以获取要读取的内存地址。
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;
}
【讨论】:
#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;
}
【讨论】:
## 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();
}
【讨论】:
不使用+,*运算符实现加、乘的代码;
对于减法,将数字的 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;
}
【讨论】:
这可以递归完成:
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;
}
【讨论】:
问题询问如何将两个数字相加,所以我不明白为什么所有解决方案都提供两个整数相加?如果这两个数字是浮点数,即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)
【讨论】:
int add_without_arithmatic(int a, int b)
{
int sum;
char *p;
p = (char *)a;
sum = (int)&p[b];
printf("\nSum : %d",sum);
}
【讨论】:
这是一个紧凑的 C 解决方案。有时递归比循环更具可读性。
int add(int a, int b){
if (b == 0) return a;
return add(a ^ b, (a & b) << 1);
}
【讨论】:
根据上面给出的答案,可以用单行代码完成:
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 & b) << 1); 的现有答案添加任何内容。它只是将if 替换为?:。
您可以使用双否定来添加两个整数,例如:
int sum2(int a, int b){
return -(-a-b);
}
【讨论】:
without using [any] arithmetic operator?
在不使用任何运算符的情况下将两个整数相加可以通过以下不同方式完成:
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;
}
【讨论】: