Java Baeldung中的按位算术

问候亲爱的读者。



很少,但面试和培训中的任务仍然具有实际价值。因此,我需要在Java中实现对整数值进行算术运算的替代方法。幸运的是,搜索引擎的第一页充满了针对位模拟的现成解决方案,您不必为其中的大多数而费解。



坦率地说,我对哈布雷(Habré)缺少此类材料感到有些惊讶(我看上去很糟吗?),因此决定用我的评论和补充内容弥补这一缺点。

请注意,在具有按位运算的示例中,值被截断为半字节:不会有根本的区别,但更易于理解。



加成



算法:



private int add(int summand, int addend)
{
	/*.*/
	int carry	= 0x00;

	/*   ,       .*/
	while(addend != 0x00)
	{
		/*      .*/
		carry	= (summand & addend);
		/*    ,     .*/
		summand	= summand ^ addend;
		/*    .*/
		addend	= (carry << 1);
	}
	return summand;
}


首先,您需要记住有关转移到高级类别的信息。在十进制系统中,其定义(对于当前数字)包括从10到18范围内的值:



109 +
019 =
128


在二进制系统中,大于1的所有事物都将被传输:



0001 +
0001 =
----
0010




或像这样:

0011 +
0011 =
----
0110


在最后一个示例中,最低有效位首先添加:



1 + 1 = 10 ()




当前位保留为零,而最高位则归零,此外它还参与其中:

1 + 1 + 1 = 11 ()


最低的仍然是当前的,而较老的则进一步。从这里,您可以推断出以下规则:在二进制系统中,对于一个当前位,值从2到3(包括3和3)(包括在内)属于进位。



在有关算法的部分中,值得关注的是使用先前值的示例确定传输存在/不存在的指令:



carry	= (summand & addend); 
0011	= 0011 & 0011


这还不是传输,而是在数字上设置“标志”,加上这些数字将留下传输,即。当两位相同时,加进位。



一旦事情已经解决,现在应该从考虑传输的位中释放第一项:



summand	= summand ^ addend;
0000	= 0011 ^ 0011


如您所知,按位XOR运算符仅在位值相反时才设置位。



循环的第三步是将进位标志直接转换为进位。为此,将它们向左移动一步,并分配给第二项:



addend = (carry << 1);
0110	= (0011 << 1);


在下一次迭代中,传输将不固定,因为:



carry	= (summand & addend); 
0000	= 0000 & 0110


第二步将给我们:



summand	= summand ^ addend;
0110	= 0000 ^ 0110,


这是所需的总和,第三步将结束while()循环:



addend = (carry << 1);
0000	= (0000 << 1)


减法



算法:



private int sub(int minuend, int subtrahend)
{
	/* .*/
	int borrow	= 0x00;

	/*   ,       .*/
	while(subtrahend != 0x00)
	{
		/*      .*/
		borrow = ((~minuend) & subtrahend);
		/*   ,     .*/
		minuend = minuend ^ subtrahend;
		/*    .*/
		subtrahend = (borrow << 1);
	}
	return minuend;
}


与前一个等效,唯一的区别是从最高有效位借用位是逆蕴涵反转好吧,我们重命名局部变量。



乘法



算法:



public int multiply(int mul1, int mul2)
{
	int result = 0;
	/*     .*/
	while(mul2 != 0)
	{
		/*     ...*/
		if ((mul2 & 1) == 1)
		{
			/*     .*/
			result = add(result, mul1);
		}
		/*     ("")*/
		mul1 <<=  1;
		/*          if()*/
		mul2 >>>= 1;
	}
	return result;
}


回到基础:十进制长乘:



  25 *
  05 =
  --
 125 +
 00
 ---
 125


那些。我们将第二个因子的每个数字乘以第一个因子。从这里开始,最高有效位的乘积向左移动了一步。最后,添加中间值。同样的事情发生在按位级别上:



    0110 *
    0011 =
    ----
    0110 +
  0 110  +
 00 00   +
000 0    +
  - ----
  1 0010


我们得出的结论是,算法仅需在遇到第二个因子中的某个设置位时就将第一个因子添加到自身中,当然要考虑到转移到最高有效位的规则:



if ((mul2 & 1) == 1) //(0011 & 0001) == 0001
{
     result = add(result, mul1); //0110 = 0000 + 0110
}


然后,第一个乘数向左移动一位(我们形成一个“阶梯”):



mul1 <<=  1; //1100 = 0110 << 1;


现在,我们确定第二个因素中的第二个最重要的位是否存在:



mul2 >>>= 1; //0001 = 0011 >>> 1;


循环运行直到第二个因子为零。我想提请您注意以下内容:该算法的一个实例在资源之间移动,其中第二个因子(mul2 >>> = 1)的逻辑移位被算术一个(mul2 >> = 1)代替它可能源自C / C ++中的实现,其中存在unsigned关键字,这样的替换不是问题。但是,在Java中,所有类型的数字都是带符号的,并且如果第二个因子由负值表示,则这种疏忽将导致算法陷入无限循环,因为 第二个因素将始终满足其条件:



1000 >>1; //  
1100 >>1; //  ( 0100)
1110 >>1; // 0010
1111 >>1; //         -1




算法:



private int divide(int dividend, int divisor)
{
	/*,        .*/
	int quotient = 0x00, mask = 0x01;
	/*     .*/
	int temp = divisor;
	/*      .*/
	int sign = ((dividend < 0)^(divisor < 0)) ? sign = -1 : 1;
	
	/*  .*/
	dividend	= dividend	< 0 ? add((~dividend), 1) : dividend;
	divisor		= divisor	< 0 ? add((~divisor), 1) : divisor;
	
	/* 0    .*/
	if (dividend < divisor) return quotient;

	while(dividend > 0 || divisor != 0)
	{
		/*    .*/
		if ((dividend >= (divisor << 0x01)) && ((divisor << 0x01) > 0))
		{
			divisor	<<= 0x01;
			mask	<<= 0x01;
		}
		/* .*/
		else
		{
			/*  ""  .*/
			quotient = quotient | mask;
			/*     .*/
			dividend = sub(dividend, divisor);
			/*      .*/
			divisor	= temp;
			mask	= 0x01;
		}
	}
	return multiply(quotient, sign);
}


这个部门的工作没有以前的例子那么顺利:我不得不写一辆自行车(不是很费力)。



只要商数大于或等于零,除法就是除数除以除数。使用这种方法,定义一个计数器就足够了,其值在每次减法运算后都会增加。其最终值是特定的:



count = 0;
1) 7 - 3 = 4; count = 1;
2) 4 - 3 = 1; count = 2;

7 / 3 = count;


当向设备提供有限的指令序列资源时,这种方法的缺点变得尤其明显,例如:2147483647/1; 2147483647/2; 2147483647/3,看起来该应用程序已冻结。

在位级别工作将更加高效。但是找到的唯一解决方案的缺点是,为了进行正确的推断,需要变量类型,即超出覆盖范围的一步,即如果输入,则局部变量的类型必须为int,否则将大值相除的结果将令人惊讶。在我的情况下,由于缺少长型而使情况更加恶化。



但是回到我们的公羊。



要了解算法的工作原理,您需要记住列划分顺序:



 = 6,  = 2;
0110|0010
     ----

 110|10   //     

    ,   ,      :

1) (1 >= 10) ->   ,    
110|10
    --
    0

2) (11 >= 10) ->  ,    
 110|10
-10  --
 --  01
 01



这里更详细。在排气处,我们得到以下信息:我们将分压器“推”到左侧,直到排出,使分度与可除数之间的差异最小



2.1) 0110 / 0010 -> 0110 - 0100 = 0010 = 2.

3) (10 >= 10) ->  ,      
 110|10
-10  --
 --  011
 010
 -10
  --
  00


该机制在算法中实现。在while()循环中,除数必须向左移动,直到满足上述规则。与此同时,私有遮罩也被移动。分支运算符if()说:“如果仅此操作的结果不违反基本规则,则可以移动。” 对负数进行额外检查的原因是,在Java中,最高有效位集是负数。没有它,循环将下降到无穷大:



//((divisor << 0x01) > 0)    , 

1) 0111 >= 0010<<1
2) 0111 >= 0100<<1
3) 0111 >= 1000<<1 //! -  if() ,     ,   .
4) 0111 >= 0000<<1
.... 


一旦算法停止满足if()条件,代码就会进入else,在该位置上商设置了掩码位:



quotient = quotient | mask;
0010 = 0000 | 0010


这与设置长除法的位相同。然后从除数中减去除数:



dividend = sub(dividend, divisor);
0010 = 0110 - 0100


之后,分频器和遮罩将返回其原始状态,并且循环再次开始:



divisor	= temp;
mask	= 0x01;


最后,我想谈谈其他代码。



上面的算法假定仅对由补码获得的正数进行除法:



dividend = dividend < 0 ? add((~dividend), 1) : dividend;
divisor = divisor < 0 ? add((~divisor), 1) : divisor;


在这里发生以下情况:如果数字为负,则其位取反,结果加1,我们得到一个正(绝对)值。相同的规则适用于正数,例如:



 6 = 0110 ->  ~6 = 1001 = 1001 + 1 = 1010 = -6
-6 = 1010 -> ~-6 = 0101 = 0101 + 1 = 0110 =  6


但是有一个例外-这是给定类型的最大负数,例如:



int -2147483648 ->
~1000 0000 0000 0000 0000 0000 0000 0000 =
 0111 1111 1111 1111 1111 1111 1111 1111 + 1 =
 1000 0000 0000 0000 0000 0000 0000 0000.


小心。



All Articles