精华内容
下载资源
问答
  • 二进制除法运算技巧
    2021-07-04 02:54:42

    2.5我们着重介绍了二进制整数的加、减运算,本次我们继续介绍乘、除运算。本章是迄今为止最难的一章,希望各位猿友有所收获,也别忘了“点个推荐哦”。

    引言

    运算一直是程序运行当中一个重要的环节,而在二进制的运算过程当中,加法运算又是重中之重,它基本上奠定了二进制运算的基础。因为无论是减法还是乘法,都可以由加法运算来替代,唯有除法不能由加法替代。

    了解计算机运算的规律,可以有助于我们理解很多程序代码上无法理解的内容。比如上章提到的溢出问题,在了解了加法运算的原理之后,相信猿友们都可以轻松的知道为何有些运算会得到意想不到的结果。

    这里还需要提一点的是,不同的处理器所采取的运算方式可能是有细微的差别的,因此也不能一概而论。因此我们大多时候会尽量讨论运算的抽象数学特性,抽象的东西大部分时候总是可靠的,这种特性为跨平台提供了基础,不过也并非总是如此,毕竟LZ只听说过浮点数运算标准,还没听说过整数运算标准,不知道究竟是LZ孤陋寡闻了,还是确无此物。

    正因如此,我们了解一下这些运算的抽象性,会有助于我们理解程序代码级无法理解的东西。

    无符号乘法

    无符号的乘法与加法类似,它的运算方式是比较简单的,只是也可能产生溢出。对于两个w位的无符号数来说,它们的乘积范围在0到(2w-1)2之间,因此可能需要2w位二进制才能表示。因此由于位数的限制,假设两个w位的无符号数的真实乘积为pro,根据截断的规则,则实际得到的乘积为 pro mod 2w。

    补码乘法

    与加法运算类似,补码乘法也是建立在无符号的基础之上的,因此我们可以很容易的得到,对于两个w位的补码数来说,假设它们的真实乘积为pro,则实际得到的乘积为 U2Tw(pro mod 2w)。

    上面的式子我们有一个假设,就是假设对于w位的两个补码数来说,它们的乘积的低w位与无符号数乘积的低w位是一样的。这意味着计算机可以使用一个指令执行无符号和补码的乘法运算。

    在书中给出了这一过程的证明,我们来大概看一下,这里主要应用了无符号编码和补码编码的关系,其中x’和y’分别代表x和y的补码编码。

    这里运用的主要技巧就是2w mod 2w = 0。

    乘法运算的优化

    根据我们小学所学的乘法运算,我们知道,假设两个w位的二进制数相乘,则需要进行w次与运算,然后进行w - 1次加法运算才能得到结果。从此不难看出,乘法运算的时间周期是很长的。因此计算机界的高手们想出了一种方式可以优化乘法运算的效率,就是使用移位和加法来替代乘法。

    上述优化的前提是对于一个w位的二进制数来说,它与2k的乘积,等同于这个二进制数左移k位,在低位补k个0。在书中对这一等式进行了证明,过程如下。

    这个过程主要应用了无符号编码的公式,各位猿友应该不难看懂。

    有了上面的基础,我们就可以使用移位和加法对乘法优化了。对于任意一个整数y,它总能使用二进制序列表示(假设不超过二进制的表示范围),因此我们可以将x和y乘积的二进制序列表示为如下形式(此公式在书中没有展现)。

    x * y = x * (yw-12w-1 + ... + y020) =  (x << w-1) * yw-1 +....+ (x << 0 ) * y0

    我们举个例子,对于x * 17,我们可以计算x * 16 + x = (x << 4) + x ,这样算下来的话,我们只需要一次移位一次加法就可以搞定这个乘法运算。而对于x * 14,则可以计算 x * 8 + x * 4 + x * 2 = (x << 3) + (x << 2) + (x << 1) ,更快的方式我们可以这么计算,x * 16 - x * 2 = (x << 4) - (x << 1) 。

    这里最后需要提一下的是,加法、减法和移位的速度并不会总快于乘法运算,因此是否要进行上面的优化就取决于二者的速度了。

    更多相关内容
  • 二进制运算技巧

    千次阅读 2019-07-05 10:09:59
    二进制运算常用技巧 1、 用于整数的奇偶性判断 一个整数a, a & 1 这个表达式可以用来判断a的奇偶性。二进制的末位为0表示偶数,最末位为1表示奇数。若a为奇数,则a & 1 的结果等于1。 2、 判断n是否是2的正...

    二进制运算常用技巧

    1、 用于整数的奇偶性判断

    一个整数a, a & 1 这个表达式可以用来判断a的奇偶性。二进制的末位为0表示偶数,最末位为1表示奇数。若a为奇数,则a & 1 的结果等于1。

    2、 判断n是否是2的正整数冪

    (!(n&(n-1)) ) && n

    好!看完上面的两个小例子,相信大家都有一个感性的认识。从理论上讲,如果一个数a他是2的正整数幂,那么a 的二进制形式必定为1000……(后面有0个或者多个0),那么结论就很显然了。

    3、 统计n中1的个数

    朴素的统计办法是:先判断n的奇偶性,为奇数时计数器增加1,然后将n右移一位,重复上面步骤,直到移位完毕。
    朴素的统计办法是比较简单的,那么我们来看看比较高级的办法。

    举例说明,考虑2位整数 n=11,里边有2个1,先提取里边的偶数位10,奇数位01,把偶数位右移1位,然后与奇数位相加,因为每对奇偶位相加的和不会超过“两位”,所以结果中每两位保存着数n中1的个数;相应的如果n是四位整数 n=0111,先以“一位”为单位做奇偶位提取,然后偶数位移位(右移1位),相加;再以“两位”为单位做奇偶提取,偶数位移位(这时就需要移2位),相加,因为此时没对奇偶位的和不会超过“四位”,所以结果中保存着n中1的个数,依次类推可以得出更多位n的算法。整个思想类似分治法。
    在这里就顺便说一下常用的二进制数:
    0xAAAAAAAA=10101010101010101010101010101010
    0x55555555 = 1010101010101010101010101010101(奇数位为1,以1位为单位提取奇偶位)
    0xCCCCCCCC = 11001100110011001100110011001100
    0x33333333 = 110011001100110011001100110011(以“2位”为单位提取奇偶位)
    0xF0F0F0F0 = 11110000111100001111000011110000
    0x0F0F0F0F = 1111000011110000111100001111(以“8位”为单位提取奇偶位)
    0xFFFF0000 =11111111111111110000000000000000
    0x0000FFFF = 1111111111111111(以“16位”为单位提取奇偶位)
    例如:32位无符号数的1的个数可以这样数:
    int count_one(unsigned long n)
    {
    //0xAAAAAAAA,0x55555555分别是以“1位”为单位提取奇偶位
    n = ((n & 0xAAAAAAAA) >> 1) + (n & 0x55555555);
    //0xCCCCCCCC,0x33333333分别是以“2位”为单位提取奇偶位
    n = ((n & 0xCCCCCCCC) >> 2) + (n & 0x33333333);

    //0xF0F0F0F0,0x0F0F0F0F分别是以“4位”为单位提取奇偶位
    n = ((n & 0xF0F0F0F0) >> 4) + (n & 0x0F0F0F0F);

    //0xFF00FF00,0x00FF00FF分别是以“8位”为单位提取奇偶位
    n = ((n & 0xFF00FF00) >> 8) + (n & 0x00FF00FF);

    //0xFFFF0000,0x0000FFFF分别是以“16位”为单位提取奇偶位
    n = ((n & 0xFFFF0000) >> 16) + (n & 0x0000FFFF);

    return n;

    }
    举个例子吧,比如说我的生日是农历2月11,就用211吧,转成二进制:
    n = 11010011
    计算n = ((n & 0xAAAAAAAA) >> 1) + (n & 0x55555555);
    得到 n = 10010010
    计算n = ((n & 0xCCCCCCCC) >> 2) + (n & 0x33333333);
    得到 n = 00110010
    计算n = ((n & 0xF0F0F0F0) >> 4) + (n & 0x0F0F0F0F);
    得到 n = 00000101 —————–à无法再分了,那么5就是答案了。
    4、对于正整数的模运算(注意,负数不能这么算)
    先说下比较简单的:
    乘除法是很消耗时间的,只要对数左移一位就是乘以2,右移一位就是除以2,传说用位运算效率提高了60%。
    5.左移右移:
    左移乘2,右移除以2,都是整除。5的二进制表示是101那么右移一位之后是10就是2了,是整除的,把最后一位拿掉了。左移的话就是在后面补一个零,相当于是乘以二,那么变成了1010,十进制是10
    e = e >> 1 #e右移一位,相当于除以2,与while(e>0)连用,可计算开二次方的次数,因为移到最后一位变成0

    展开全文
  • 二进制除法的实现

    万次阅读 多人点赞 2017-09-25 11:49:20
    转自:http://www.cnblogs.com/zuoxiaolong/p/computer10.html引言 运算一直是程序运行当中一个重要的环节,而在二进制运算过程当中,加法运算又是重中之重,它基本上奠定了二进制运算的基础。因为无论是减法还是...

     转自:http://www.cnblogs.com/zuoxiaolong/p/computer10.html

    引言

     

      运算一直是程序运行当中一个重要的环节,而在二进制的运算过程当中,加法运算又是重中之重,它基本上奠定了二进制运算的基础。因为无论是减法还是乘法,都可以由加法运算来替代,唯有除法不能由加法替代。

      了解计算机运算的规律,可以有助于我们理解很多程序代码上无法理解的内容。比如上章提到的溢出问题,在了解了加法运算的原理之后,相信猿友们都可以轻松的知道为何有些运算会得到意想不到的结果。

      这里还需要提一点的是,不同的处理器所采取的运算方式可能是有细微的差别的,因此也不能一概而论。因此我们大多时候会尽量讨论运算的抽象数学特性,抽象的东西大部分时候总是可靠的,这种特性为跨平台提供了基础,不过也并非总是如此,毕竟LZ只听说过浮点数运算标准,还没听说过整数运算标准,不知道究竟是LZ孤陋寡闻了,还是确无此物。

      正因如此,我们了解一下这些运算的抽象性,会有助于我们理解程序代码级无法理解的东西。

     

    无符号乘法

     

      无符号的乘法与加法类似,它的运算方式是比较简单的,只是也可能产生溢出。对于两个w位的无符号数来说,它们的乘积范围在0到(2w-1)2之间,因此可能需要2w位二进制才能表示。因此由于位数的限制,假设两个w位的无符号数的真实乘积为pro,根据截断的规则,则实际得到的乘积为 pro mod 2w

     

    补码乘法

     

      与加法运算类似,补码乘法也是建立在无符号的基础之上的,因此我们可以很容易的得到,对于两个w位的补码数来说,假设它们的真实乘积为pro,则实际得到的乘积为 U2Tw(pro mod 2w)。

      上面的式子我们有一个假设,就是假设对于w位的两个补码数来说,它们的乘积的低w位与无符号数乘积的低w位是一样的。这意味着计算机可以使用一个指令执行无符号和补码的乘法运算。

      在书中给出了这一过程的证明,我们来大概看一下,这里主要应用了无符号编码和补码编码的关系,其中x’和y’分别代表x和y的补码编码。

                                   

      这里运用的主要技巧就是2w mod 2w = 0。

     

    乘法运算的优化

     

      根据我们小学所学的乘法运算,我们知道,假设两个w位的二进制数相乘,则需要进行w次与运算,然后进行w - 1次加法运算才能得到结果。从此不难看出,乘法运算的时间周期是很长的。因此计算机界的高手们想出了一种方式可以优化乘法运算的效率,就是使用移位和加法来替代乘法。

      上述优化的前提是对于一个w位的二进制数来说,它与2k的乘积,等同于这个二进制数左移k位,在低位补k个0。在书中对这一等式进行了证明,过程如下。

                                       

     

      这个过程主要应用了无符号编码的公式,各位猿友应该不难看懂。

      有了上面的基础,我们就可以使用移位和加法对乘法优化了。对于任意一个整数y,它总能使用二进制序列表示(假设不超过二进制的表示范围),因此我们可以将x和y乘积的二进制序列表示为如下形式(此公式在书中没有展现)。

                                           x * y = x * (yw-12w-1 + ... + y020) =  (x << w-1) * yw-1 +....+ (x << 0 ) * y0

      我们举个例子,对于x * 17,我们可以计算x * 16 + x = (x << 4) + x ,这样算下来的话,我们只需要一次移位一次加法就可以搞定这个乘法运算。而对于x * 14,则可以计算 x * 8 + x * 4 + x * 2 = (x << 3) + (x << 2) + (x << 1) ,更快的方式我们可以这么计算,x * 16 - x * 2 = (x << 4) - (x << 1) 。

      这里最后需要提一下的是,加法、减法和移位的速度并不会总快于乘法运算,因此是否要进行上面的优化就取决于二者的速度了。

     

    无符号除法

     

      除法与乘法不同,它不满足加法的分配律,也就是设y = m + n , x/y != x/m + x/n。而且不幸的是,它有时候会比乘法运算更慢,但是我们只能针对除数可表示为2k的除法运算进行优化,转换为算数右移或者逻辑右移k位的运算(无符号数为逻辑右移,为正数时,逻辑右移与算术右移效果一样)。

      由于是除法,因此我们会涉及到舍入的问题。这里定义└x/y┘的值为a',x/y为a,则对于a',它是唯一一个整数,满足 a' =< a < a'+1。

      比如└2.1┘的值就为2,而对于└-2.1┘则为-3,如果本身就是整数,则等于自身。

      书中给出了无符号数除以2k等价于右移k位(w > k >= 0)的证明,这一证明过程相对比较复杂一点,LZ这里给出一个相对简单的证明方式,不采用书上的证明。如果各位看LZ的证明看不懂的话,也可以参照一下书上的方式。

      我们假设对于一个w位的无符号数来说,假设它的位表示为[xw-1....x0],则x = xw-12w-1 + .... + x020 。因此就有以下结果。

                              x/2k = xw-12w-1-k +... + xk20 + xk-12-1 +...+ x02-k = B2Uw-k([xw-1....xk]) + xk-12-1 +...+ x02-k

      由于xk-12-1 +...+ x02-k <= 2-1 + .... 2-k = (1-(1/2)k) < 1 (这里是证明的关键一步,先假设所有位为1,则利用等比数列求和公式即可得到),因此有└xk-12-1 +...+ x02-k┘ = 0。

      因此我们可以得到└x/2k┘ = └B2Uw-k([xw-1....xk])┘ + └xk-12-1 +...+ x02-k┘ = └B2Uw-k([xw-1....xk])┘ = B2Uw-k([xw-1....xk]) = x >> k。

      更直观的,我们可以使用程序验证这一结果,看下面的Java代码。

    复制代码
    public class Main {
        
        public static void main(String[] args) {
            int a = 17;
            int b = 8;
            int c = a/b;
            System.out.println("a:" + Integer.toBinaryString(a));
            System.out.println("b:" + Integer.toBinaryString(b));
            System.out.println("c:" + Integer.toBinaryString(c));
            System.out.println("a >> 3:" + Integer.toBinaryString(a >> 3));
        }
    }
    复制代码

      这段程序的结果如下,可以看出a/b的结果就是a右移3位的结果,也就是结果等于a >> 3。

      

    补码除法

     

      由于刚才我们的程序使用的都是正数,因此虽然Java中没有无符号数,不过我们可以模拟出无符号数的效果。也可以认为,补码除法在被除数为正数的情况下,与无符号编码是一样的效果(我们不考虑除数为负的情况,因为被除数与除数的符号位可以相互抵消,以下也一样),不过当被除数为负数时就不同了。这里在介绍补码除法之前,我们先来看一下,当a为负数时的结果,也就是此时会采用补码编码。

      我们将刚才的程序稍微修改一下,如下。

    复制代码
    public class Main {
        
        public static void main(String[] args) {
            int a = -17;
            int b = 8;
            int c = a/b;
            System.out.println("a:" + Integer.toBinaryString(a));
            System.out.println("b:" + Integer.toBinaryString(b));
            System.out.println("c:" + Integer.toBinaryString(c));
            System.out.println("a >> 3:" + Integer.toBinaryString(a >> 3));
            System.out.println("c:" + c);
            System.out.println("a >> 3:" + (a >> 3));
        }
    }
    复制代码

      它得到的结果如下,有点出乎意料。

      这次为了便于观看,我们将c和a >> 3的整数值打印了出来,发现移位运算的结果是-3,而a/b的结果为-2。可以看出我们a/b的结果是我们所期望的,可是移位的运算结果似乎在舍入的时候出现了问题。

      其实这个问题出现的原因很简单,补码编码与无符号编码类似,对于位表示都有└x/2k┘= B2Tw-k([xw-1....xk]) = x >> k。不过此时由于是负数,所以采取了向下舍入。上面已经提到过└-2.1┘的值为-3。

      因此,我们得到这样一个结论,当有舍入发生时,将一个负数右移k位不等价于把它除以2k

     

    除法的补救

     

      既然在舍入时,一个负数右移k位不等价于把它除以2k。那么为了使用这种优化,计算机界的大神们自然要想办法解决这个问题。于是他们想出了一个办法,即“偏置”这个值(不得不佩服这些大神们)。

      首先我们定义┌x/y┐的值为a',x/y为a,则对于a',它是唯一一个整数,满足 a'-1 < a <= a'。

      在上面的定义基础上,“偏置”的含义就是,我们有┌x/y┐ = └(x+y-1)/y┘。这一过程的证明不难理解,我们假设x = ky + r(我们考虑r > 0 ,此时会有舍入发生),则有。

                       └(x+y-1)/y┘ = └(ky+r+y-1)/y┘ = k + └(r+y-1)/y┘ = k + 1

      可以看出在做了这个处理之后,也就是将x加上y-1的偏移量,此时在舍入时,结果会在原来的基础上加1。这也正是“偏置”的含义所在,它会将舍入“偏置”到向上舍入。

      下面我们将补码除法当中的程序按照这种方式修复一下,看是不是这个结果,如下。

    复制代码
    public class Main {
        
        public static void main(String[] args) {
            int a = -17;
            int b = 8;
            int c = a/b;
            System.out.println("a:" + Integer.toBinaryString(a));
            System.out.println("b:" + Integer.toBinaryString(b));
            System.out.println("c:" + Integer.toBinaryString(c));
            System.out.println("(a+b-1) >> 3:" + Integer.toBinaryString((a+b-1) >> 3));
            System.out.println("c:" + c);
            System.out.println("(a+b-1) >> 3:" + ((a+b-1) >> 3));
        }
    }
    复制代码

      此处我们将a“偏置”,也就是加上b-1的偏移量,我们来看结果。

      可以看出,在偏置之后,在负结果舍入时,移位运算的结果将会是我们期望得到的,这样我们便可以使用这一技巧进行优化了。

    展开全文
  • 二进制基础及位运算

    千次阅读 2019-12-04 16:06:09
    二进制计算 每一位上的数基数的索引次幂相加之和 例如:0101=12º+12²=5 第一位1基数2的索引0次幂+第三位1*基数2的2次幂等于5 其他进制计算等同 十进制转2进制:2求余法 2求余倒序表示 简便算法:记住2的10次...

    一、什么是二进制

    二进制是计算机运算时所采用的数制,基数是2,也就是说它只有两个数字符号,即0和1。如果在给定的数中,除0和1外还有其他数(例如1061),那它就绝不会是一个二进制数了。二进制数的最大数码也是基数减1,即2-1=1,最小数码也是0。二进制数的标志为B,如(1001010)B,也可用下标“2”来表示,如(1001010)2(注意是下标)。

    二、二进制转换为十进制

    二进制转换成十进制的方法,大家可能早就有所了解了,如在IPv4地址计算时就经常进行这样的操作。转换的方法比较简单,只需按它的权值展开即可。展开的方式是把二进制数首先写成加权系数展格式,然后按十进制加法规则求和。这种方法称为“按权相加”法。

    二进制整数部分的一般表现形式为:bn-1…b1b0(共n位),按权相加展开后的格式为(注意,展开式中从左往右各项的幂次是从高到低下降的,最高位的幂为n-1,最低的幂为0):

    bn-1×2n-1+bn-2×2n-2…+b1×21+b0×20

    如二进制数(11010)2的按权相加展开格式为:

    在这里插入图片描述
    用一句话概括来说就是:二进制数*基数的索引次幂相加之和

    二进制小数部分的幂次是反序排列的(也就是与整数部分的幂次序列相反,从左往右其绝对值是从低到高上升的),且为负值,最高位幂次(也就是最靠近小数点的第一个小数位的幂次)为“-1”。如二进制小数部分的格式为:0.bn-1…b1b0,则按权相加后的展开格式为:

    bn-1×2-1+bn-1×2-2…+b1×2-(n-1)+b0×2-n

    如(0.1011)2的按权相加展开格式为:

    1×2-1+0×2-2+1×2-3+1×2-4=0.5+0+0.125+0.0625=(0.6875)10

    三、十进制转二进制

    十进制整数转换为二进制的方法是:采用“除2逆序取余”法(采用短除法进行)。也就是先将十进制数除以2,得到一个商数(也是下一步的被除数)和余数;然后再将商数除以2,又得到一个商数和余数;以此类推,直到商数为小于2的数为止。然后从最后一步得到的小于2的商数开始将其他各步所得的余数(也都是小于2的0或1)排列起来(俗称“逆序排列”)就得到了对应的二进制数。

    在这里插入图片描述
    图 1-1 十进制整数48转换成二进制整数的步骤
    在这里插入图片描述
    图 1-2 十进制整数250转换成二进制整数的步骤

    简便算法:记住2的10次幂1024内的次幂值,比如计算114的二进制
    在这里插入图片描述

    四、二进制逻辑运算

    在计算机中所有数据都是以二进制的形式储存的。位运算其实就是直接对在内存中的二进制数据进行操作,因此处理数据的速度非常快。
    在实际编程中,如果能巧妙运用位操作,完全可以达到四两拨千斤的效果,正因为位操作的这些优点,所以位操作在各大IT公司的笔试面试中一直是个热点问题。因此本文将对位操作进行如下方面总结:

    • 位操作基础,用一张表描述位操作符的应用规则并详细解释。
    • 常用位操作小技巧,判断奇偶、交换两数、变换符号、求绝对值。
    • 位操作与空间压缩,针对筛素数进行空间压缩。

    1、位操作基础

    基本的位操作符有与、或、异或、取反、左移、右移、无符号右移这7种,它们的运算规则如下所示:

    符号描述运算规则
    &遇0则0
    |遇1则1
    ~求反,1变0,0变1
    ^异或不进位加(相同为0,相异为1)
    >>右移左补符号位
    <<左移右补0
    >>>无符号右移左补0
    注:
    1、在这6种操作符,只有~取反是单目操作符,其它5种都是双目操作符。
    2、位操作只能用于整形数据,对float和double类型进行位操作会被编译器报错。
    3、位操作符的运算优先级比较低,因此尽量使用括号来确保运算顺序,否则很可能会得到莫明其妙的结果。比如要得到像1,3,5,9这些2^i+1的数字。写成int a = 1 << i + 1;是不对的,程序会先执行i + 1,再执行左移操作。应该写成int a = (1 << i) + 1;
    4、另外位操作还有一些复合操作符,如&=、|=、 ^=、<<=、>>=。
    

    2、常用位操作小技巧

    下面对位操作的一些常见应用作个总结,有判断奇偶、交换两数、变换符号及求绝对值。这些小技巧应用易记,应当熟练掌握。

    1.判断奇偶

    只要根据最末位是0还是1来决定,为0就是偶数,为1就是奇数。因此可以用
    if ((a & 1) == 0 )代替if (a % 2 == 0)来判断a是不是偶数。
    下面程序将输出0到100之间的所有奇数。

        static void oddOrEven() {
            IntStream.rangeClosed(1, 100)
                    .filter((i) -> (i & 1) == 1)
                    .forEach(System.out::println);
        }
    

    2.交换两数

    一般的写法是:

    static void swap(int a, int b) {
    	//定义一个临时变量
    	int c = a;
    	a = b;
    	b = c;
    }
    
    static void swap(int a, int b) {
            a = a^b;
            b = a^b;
            a = a^b;
            System.out.println("a=" + a + ",b= " + b);
        }
    

    可以这样理解:
    第一步:a = a^b
    第二部:b = a^b = (a^b)^b=第一步的值b。由于运算满足交换律,(a^b)^b=a^b^b。由于一个数和自己异或的结果为0并且任何数与0异或都会不变的,所以此时b被赋上了a的值。
    第三步 a=a^b,由于前面二步可知a=(a^b),b=a,所以a=a^b即a=(a^b)^a。故a会被赋上b的值。

    再来个实例说明下以加深印象。int a = 13, b = 6;
    a的二进制为 13=8+4+1=1101(二进制)
    b的二进制为 6=4+2=0110(二进制)
    第一步 a=a^b a = 1101 ^ 0110 = 1011;
    第二步 b=a^b b = 1011 ^ 0110 = 1101;即b=13
    第三步 a=a^b a = 1011 ^ 1101 = 0110;即a=6

    3.变换符号

    变换符号就是正数变成负数,负数变成正数。
    如对于-11和11,可以通过下面的变换方法将-11变成11
    1111 0101(二进制) –取反-> 0000 1010(二进制) –加1-> 0000 1011(二进制)
    同样可以这样的将11变成-11
    0000 1011(二进制) –取反-> 1111 0100(二进制) –加1-> 1111 0101(二进制)
    因此变换符号只需要取反后加1即可。完整代码如下:

    	static void signReversal(int a) {
            a = ~a + 1;
            System.out.println(a);
        }
    

    4.求绝对值

    位操作也可以用来求绝对值,对于负数可以通过对其取反后加1来得到正数。对-6可以这样:
    1111 1010(二进制) –取反->0000 0101(二进制) -加1-> 0000 0110(二进制)
    来得到6。
    因此先移位来取符号位,int i = a >> 31;要注意如果a为正数,i等于0,为负数,i等于-1。然后对i进行判断——如果i等于0,直接返回。否之,返回~a+1。完整代码如下:

    	static void abs(int a) {
            a = (a >> 31) == 0 ? a : ~a + 1;
            System.out.println(a);
        }
    

    现在再分析下。对于任何数,与0异或都会保持不变,与-1即0xFFFFFFFF异或就相当于取反。因此,a与i异或后再减i(因为i为0或-1,所以减i即是要么加0要么加1)也可以得到绝对值。所以可以对上面代码优化下:

        static void absNoDecide(int a) {
            int i = a >> 31;
            a = (a ^ i) - i;
            System.out.println(a);
        }
    

    注意这种方法没用任何判断表达式,因此建议读者记住该方法(^_^讲解过后应该是比较好记了)。

    5、获得int最大值

    System.out.println((1 << 31) - 1);// 2147483647, 由于优先级关系,括号不可省略
    System.out.println(~(1 << 31));// 2147483647
    

    6、获得int型最小值

    System.out.println(1 << 31);
    System.out.println(1 << -1);
    

    7、获得long类型的最大值

    System.out.println(((long)1 << 127) - 1);
    

    8、乘以2运算

    System.out.println(n << 1);
    

    9、除以2运算(负奇数的运算不可用,精度缺失)

    System.out.println(n >> 1);
    

    10、m乘以2的n次方

    System.out.println(m << n);
    

    11、m除以2的n次方

    System.out.println(m >> n);
    

    12、取两个数的最大值(某些机器上,效率比a>b ? a:b高)

    System.out.println(b & ((a - b) >> 31) | a & (~(a - b) >> 31));
    

    13、取两个数的最小值(某些机器上,效率比a>b ? b:a高)

    System.out.println(a & ((a - b) >> 31) | b & (~(a - b) >> 31));
    

    14、判断符号是否相同(true 表示 x和y有相同的符号, false表示x,y有相反的符号。)

    System.out.println((a ^ b) > 0);
    

    15、计算2的n次方 n > 0

    System.out.println(2 << (n - 1));
    

    16、判断一个数n是不是2的幂

    /*如果是2的幂,n一定是100... n-1就是1111....所以做与运算结果为0*/
    System.out.println((n & (n - 1)) == 0);
    

    17、求两个整数的平均值

    System.out.println((a + b) >> 1);
    

    18、从低位到高位,取n的第m位

    int m = 2;
    System.out.println((n >> (m - 1)) & 1);
    

    19、从低位到高位.将n的第m位置为1

    /*将1左移m-1位找到第m位,得到000...1...000 n在和这个数做或运算*/
    System.out.println(n | (1<<(m-1)));
    

    20、从低位到高位,将n的第m位置为0

    /* 将1左移m-1位找到第m位,取反后变成111...0...1111 n再和这个数做与运算*/
    System.out.println(n & ~(0 << (m - 1)));
    

    常见操作技巧

    取高位右移,取低位相与

    3、位操作与空间压缩

    筛素数法在这里不就详细介绍了,本文着重对筛素数法所使用的素数表进行优化来减小其空间占用。要压缩素数表的空间占用,可以使用位操作。下面是用筛素数法计算100以内的素数示例代码(注2):

    	static void getPrime() {
            int max = 100;
            boolean[] flag = new boolean[100];
            int[] primes = new int[max / 3 + 1];
    
            int i, j;
            int pi = 0;
            for (i = 2; i < max; i++) {
                if (!flag[i]) {
                    primes[pi++] = i;
                    /**
                     * 对于每个素数,它的倍数必定不是素数
                     */
                    for (j = i; j < max; j += i)
                        flag[j] = true;
                }
            }
    
            Arrays.stream(primes)
                    .limit(pi)
                    .forEach((p) -> System.out.print(p + ","));
        }
    

    在上面程序是用boolean数组来作标记的,boolean型数据占1个字节(8位),因此用位操作来压缩下空间占用将会使空间的占用减少八分之七。
    下面考虑下如何在数组中对指定位置置1,先考虑如何对一个整数在指定位置上置1。对于一个整数可以通过将1向左移位后与其相或来达到在指定位上置1的效果,代码如下所示:

    //在一个数指定位置上置1
    int i = 0;
    i |= i << 10;
    

    同样,可以1向左移位后与原数相与来判断指定位上是0还是1(也可以将原数右移若干位再与1相与)。

    //判断指定位上是0还是1
    int i = 1 << 10;
    if ((i & (1 << 10)) != 0) {
    	System.out.println("指定位上为1");
    } else {
    	System.out.println("指定位上为0");
    }
    

    扩展到数组上,我们可以采用这种方法,因为数组在内存上也是连续分配的一段空间,完全可以“认为”是一个很长的整数。先写一份测试代码,看看如何在数组中使用位操作:

    		int[] bits = new int[40];
    
            for (int m = 0; m < 40; m += 3) {
                bits[m / 32] |= (1 << (m % 32));
            }
    
            // 输出整个bits
            for (int m = 0; m < 40; m++) {
                if (((bits[m / 32] >> (m % 32)) & 1) != 0) {
                    System.out.print('1');
                } else {
                    System.out.print('0');
                }
            }
    

    运行结果如下:
    1001001001001001001001001001001001001001

    可以看出该数组每3个就置成了1,证明我们上面对数组进行位操作的方法是正确的。因此可以将上面筛素数方法改成使用位操作压缩后的筛素数方法:

    	static void getPrimeByBitwise() {
            int max = 100;
            int[] flag = new int[max/32 + 1];
            int[] primes = new int[max / 3 + 1];
    
            int i, j;
            int pi = 0;
            for (i = 2; i < max; i++) {
                if ((((flag[i/32] >> (i % 32))& 1) == 0)) {
                    primes[pi++] = i;
                    /**
                     * 对于每个素数,它的倍数必定不是素数
                     */
                    for (j = i; j < max; j += i)
                        flag[j/32] |= (1 << (j % 32));
                }
            }
    
            Arrays.stream(primes)
                    .limit(pi)
                    .forEach((p) -> System.out.print(p + ","));
        }
    
    展开全文
  • LeetCode190题:颠倒二进制位 题目描述: 颠倒给定的 32 位无符号整数的二进制位。 示例 1: 输入: 00000010100101000001111010011100 输出: 00111001011110000010100101000000 解释: 输入的二进制串 ...
  • 二进制加,减法,23个位运算技巧

    千次阅读 2019-04-06 20:36:22
    二进制加,减法 二进制最高位为1时表示负数,为0时表示正数。 **原码:**一个正数,转换为二进制位就是这个正数的原码。负数的绝对值转换成二进制位然后在高位补1就是这个负数的原码。 举例说明:  int类型的 3 的...
  • 二进制,位运算(原码、反码、补码)

    千次阅读 2021-02-23 13:10:52
    1 二进制 2 原码、反码、补码 3 位运算符 4 位运算符使用技巧 上回学习运算符时,漏了位运算符,因为位运算符理解起来稍微有点复杂,所以要单独写一篇~ 要理解按位运算符,要先了解计算机进行存储和计算的底层逻辑。...
  • 二进制计算

    千次阅读 2021-07-21 05:31:49
    虽然很早就接触了二进制,却一直没有正视这个问题,阅读《计算机科学导论》的时候,基本上是跳过了这一部分,总是以“这么基础的东西,反正工作中基本用不上”的理由给搪塞过去。最近正在阅读《编码》和《程序员的...
  • 二进制  正整数的二进制表示 (假定类型是byte)  正整数的二进制表示与此类似, 只是在十进制中,每个位置可以有10个数字,从0到9,但在二进制中,每个位置只能是0或1。  例如: 0000 1010 ==> 10 负整数...
  • 在学习框架源码底层时,有非常多的二进制运算,由于大学学习计算机基础时抓梦脚(jio),没有学习牢固,所以在看底层源码的算法逻辑时遇到二进制 运算比较吃力,遂通过一篇博文来总结下二进制运算,记录一下。 正文 ...
  • 因此不管是减法还是乘法,都能够由加法运算来取代,只有除法不能取代. 了解了计算机运算的规律,能够有助于我们理解非常多程序代码上无法理解的内容能够.比方上一张提出的溢出问题,在了解了加法运算的原理之后,相信...
  • acm二进制运算技巧

    2017-08-11 20:24:41
    运算方法有六种: & 与运算  | 或运算  ^ 异或运算  ~ 非运算(求补)  >> 右移运算    运用这些基本的运算,我们可以解决acm所需的各种运算,给Bit赋1,赋0,给他的值取反,还有好多段操作。如下...
  • 计算机二进制运算

    千次阅读 2016-01-30 13:48:59
    在计算机中,参与运算的是二进制数的补码形式。另外在计算机中规定:正数 原码 == 反码 == 补码;负数和0 反码 == 其原码符号位之外的各位取反; 补码 == 反码 + 1 (如果+1之后有进位的,要一直往前进位,包括...
  • 《计算机应用基础第四章《二进制的学习》课件.ppt》由会员分享,可在线阅读,更多相关《计算机应用基础第四章《二进制的学习》课件.ppt(26页珍藏版)》请在人人文库网上搜索。1、信息工程系,讲课人:*,计算机应用基础...
  • 进制之间快速转换技巧

    万次阅读 多人点赞 2019-03-30 12:52:55
    1.十进制、二进制、八进制、十六进制对应数值如下表。 进制换算表 十进制 二进制 八进制 十六进制 0 0 0 0 1 1 1 1 2 10 2 2 3 11 3 3 4 100 4 ...
  • 二进制的在算法实战
  • Java位运算符及二进制常识

    千次阅读 2021-03-18 00:21:45
    一、位运算 二、位移运算 三、二进制数以Java中最常使用的int类型为例(32位)。 ㈠ 符号位二进制数最左端的数字为符号位:0代表正,1代表负。㈡ 最大与最小⑴ 1是最小的正整数,符号位为0,最后一位为1,其它全部为0...
  • 这一篇将会了解一下计算机底层,二进制. 二进制 什么是二进制? 二进制是计算技术中广泛采用的一种数制 ...计算机底层计算使用的是二进制(没有学过二进制,数学比较差可能理解比较难) 也就是说,我...
  • 二进制与十进制互相转换,通俗易懂!保姆式教学!
  • 二进制常识

    2021-05-19 20:18:47
    为什么计算机用二进制 cpu只能通过低电频(0)和高电频(1)进行读取和记录,所以计算机就用0 1作为自己的语言,对事件万物进行描述。 常识 1.常见的二进制单位 bit:(b) 最基本单位 可以表示0 或者1 byte:(B)...
  • 将十进制数转换成二进制形式(比较复杂)下面用递归方式来实现将十进制数转换成二进制形式的字符串,在编写这个程序之前,必须先了解熟知的进制表示原理,同样一个数值,可以用多种进制进行表示,就好比同样的钱,可以...
  • 数字化信息编码的概念和二进制编码的知识一、数字化信息编码的概念1.信息: 计算机能够处理的如数值、文字、符号、语音、图形等数据称为信息。2.编码: 就是用少量、简单的基本符号,选用一定的组合规则,以表示大量...
  • bcd码和二进制码有什么区别

    千次阅读 2021-07-26 01:06:10
    描述二进制是由1和0两个数字组成的,它可以表示两种状态,即开和关。所有输入电脑的任何信息最终都要转化为二进制。目前通用的是ASCII码。最基本的单位为bit。二进制编码是用预先规定的方法将文字、数字或其他对象编...
  • 二进制、八进制、十进制、十六进制关系及转换

    万次阅读 多人点赞 2019-02-21 21:20:22
    二进制,八进制,十进制,十六进制之间的关系是什么?浮点数是什么回事? 本文内容参考自王达老师的《深入理解计算机网络》一书&amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;中国水利水电出版社&amp;amp;amp;amp...
  • 对于任何一种进制—X进制,就表示每一位上的数运算时都是逢X进一位。 是逢十进一,是逢十六进一,就是逢进一,以此类推,x进制就是逢x进位。我们一般生活或学习中最长遇到的进制为,但是这只是常见的进制,并不是...
  • ACM二进制运算技巧

    千次阅读 2014-08-23 22:11:50
    运算方法有六种: & 与运算  | 或运算  ^ 异或运算  ~ 非运算(求补)  >> 右移运算    运用这些基本的运算,我们可以解决acm所需的各种运算,给Bit赋1,赋0,给他的值取反,还有好多段操作。如下...
  • 2.5我们着重介绍了二进制整数的加、减运算,本次我们继续介绍乘、除运算。本章是迄今为止最难的一章...因为无论是减法还是乘法,都可以由加法运算来替代,唯有除法不能由加法替代。  了解计算机运算的规律,可以有助
  • **B表示二进制 ** (010100101)B O、Q表示8进制 (12565)O或者是(12565)Q H表示16进制 (56BBA)H //正数拓展 进制 二进制 0b 十进制 八进制 0 十六进制 0x int a=10; int a1=0b10; int a2=010; int a3=0x10; int ...
  • 十六进制(简写为hex或下标16)在数学中是一种逢16进...1.将二进制、八进制、十六进制转换为十进制 二进制、八进制和十六进制向十进制转换都非常容易,就是“按权相加”。所谓“权”,也即“位权”。 示例1:十六进..

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,623
精华内容 4,649
热门标签
关键字:

二进制除法运算技巧