zoukankan      html  css  js  c++  java
  • 三分钟熟悉进制转换与位运算

    进制和位运算简介

    进制也叫进位制,是一种记数方法,也称进位计数法,利用这种记数法可以使用有限的数字符号来表示所有的数值。

    一种进制中可以使用的数字符号的数目称为这种进位制的基数,若一个进制的基数为 N,则可称之为 N 进制,即表示数值时满 N 进一。

    在生活中最常用的是十进制,使用 10 个阿拉伯数字 0 到 9 进行记数。而在电子计算机领域,内部使用的是二进制,电路的状态通过 0 和 1 表示来实现记数。八进制和十六进制计算机领域也较为常用,尤其十六进制。

    位运算则是在程序中对二进制数的一元和二元运算操作。

    在 JDK 以及框架源码中都存在进制转换和位运算的身影,作为开发人员应该熟悉基本的进制转换和位运算(最起码得能看懂吧)。

    进制转换

    例如,十进数的 13,二进制的 1101,他们表示相同的数值,只是不同的表现形式而已,那么不同进制之间如何相互转换呢?

    十进制转换 N 进制,可以通过“短除法”求余数然后倒序得到转换结果,一个十进制数转换为 N 进制就除以 N,例如:

    1. 将十进制数 123 转换为二进制。

    01

    结果:1111011

    1. 将十进制数 123 转换为十六进制。

    02

    结果:7b

    N 进制转为十进制可以通过“按位权展开法”来转换,即在 N 进制中,每个位置的数字乘以进制的基数为底的所处位置序号(从 0 开始)为指数的整数次幂,然后相加。例如:

    1. 将二进制数 1111011 转换为十进制。

    1 * 2^6 + 1 * 2^5 + 1 * 2^4 + 1 * 2^3 + 0 * 2^2 + 1 * 2^1 + 1 * 2^0
    = 1 * 64 + 1 * 32 + 1 * 16 + 1 * 8 + 0 * 4 + 1 * 2 + 1 * 1
    = 123

    1. 将十六进制数 7b 转换为十进制

    7 * 16^1 + b * 16^0
    = 7 * 16 + 11 * 1
    = 123

    二进制与十六进制互转

    四位二进制数表示的范围是 0 - 15,用四位二进制数可以表示十六进制的一位数,反之亦然。即二进制转十六进制可以通过四(位)合一(位),十六进制转二进制可以通过一(位)拆四(位)来转换。例如:
    二进制 1111011 转换为十六进制,0111(7) - 1011(b) 即转换结果为7b。
    十六进制 7b 转为二进制,7(0111) - b(十进制 11,二进制 1011) 即转换结果为 1111011。

    二进制与八进制互转

    三位二进制数表示的范围是 0 - 7,用三位二进制数可以表示八进制的一位数,反之亦然。即二进制转八进制可以通过三(位)合一(位),八进制转二进制可以通过一(位)拆三(位)来转换。例如:
    二进制 1111011 转换为八进制,001(1) - 111(7) - 011(3) 即转换结果为173。
    八进制 173 转为二进制,1(001) - 7(111) - 3(011) 即转换结果为 1111011。

    负数的二进制

    前面介绍的都是正数的二进制转换,那么负数的二进制如何表示和转换的?

    首先,我们先了解一下原码、反码、补码,一个数在计算机中的二进制表示形式叫做这个数的机器数,机器数的最高位称为符号位,正数符号位为 0,负数符号位为 1,而符号位之外部分称为机器数的真值(表示真正的数值部分),原码、反码、补码是机器数的表示方法。

    原码: 原码的表示方法,最高位是符号位,其余部分表示数值(真值)。正数符号位为 0,负数符号位为 1,0 的符号位可以为 0 或 1(+0 和 -0)。

    例如,我们用 8 位二进制表示一个数,则 +11 的原码为 00001011,-11 的原码就是 10001011。

    在数学中 1 + (-1) = 0,如果使用原码直接参与数学运算,00000001 + 10000001 = 10000010(换算成十进制为 -2),显然不对。所以原码的符号位不能直接参与运算,必须和其他位分开,这就增加了硬件的开销和复杂性。为了便于 ALU (算术逻辑单元,实现多组算术运算和逻辑运算的组合逻辑电路)的设计,又发展出反码、补码。

    反码: 反码的表示方法,正数的反码等于其原码,而负数的反码通过保留其符号位,将其原码的数值位(真值)取反。

    例如,同样用 8 位二进制表示一个数,11 = 00001011(原码) = 00001011(反码),
    -11 = 10001011(原码) = 11110100(反码)。

    虽然,反码可以消除原码存在的计算问题,由于反码存在多余的负零等问题,此方式并未被广泛应用。

    补码: 补码的表示方式,正数和 0 的补码等于其原码,且补码的 0 只有一个表示方式,不分 +0 和 -0。负数是将他的反码加 1 得到补码。

    例如,11 = 00001011(原码) = 00001011(反码) = 00001011(补码),-11 = 10001011(原码) = 11110100(反码) = 11110101(补码)。

    一种简单方式算出补码

    负数的补码一般情况下通过负数的原码得到反码,在将反码加 1 获得补码。这里有一种简单的计算补码方法。

    1. 将对应的正数原码从最低比特位向高比特位查找。
    2. 若该比特位为 0,补码对应比特位填 0,继续向高比特位查找。
    3. 若找到第一个为 1 的比特位,将补码对应比特位填 1。
    4. 然后,将其余未转换的比特位全部取反。

    例如:计算 -20 的补码。

    03

    计算机为什么使用补码

    为了简化 ALU 设计,减法转换为加法来计算,例如,1 - 1 可以转换为 1 + (-1) = 00000001 + 11111111 = 100000000 (由于加数和被加数都是 8 位,因此运算结果也限制在 8 位,前面溢出的比特位 1 忽略,所以结果为 00000000 = 0),即一个数加上另一个数的补码来表示。

    这样只要有加法电路即可完成各种有号数加减法,对于乘除法,乘法在计算机中其实就是不断的做加法,除法就是相减,本质也是加法,所以四则运算的基础都是由加法而来,电路设计得到了很大简化。补码解决了原码和反码出现的问题,因此计算机中数值使用补码方式来计算和存储的。

    Java 内置的进制转换 API

    JDK 的 Integer 和 Long 类提供了常用的进制相互转换方法。

    进制转换 java.lang.Integer java.lang.Long
    10进制 → 2进制 toBinaryString(int i) toBinaryString(long i)
    10进制 → 8进制 toOctalString(int i) toOctalString(long i)
    10进制 → 16进制 toHexString(int i) toHexString(long i)
    10进制 → n 进制 toString(int i, int radix) toString(long i, int radix)
    n 进制 → 10进制 valueOf(String s, int radix)
    parseInt(String s, int radix)
    valueOf(String s, int radix)
    parseLong(String s, int radix)
    System.out.println(Integer.toBinaryString(13)); // 十转二,结果为 1101
    System.out.println(Integer.toOctalString(13));  // 十转八,结果为 15
    System.out.println(Integer.toHexString(13));    // 十转十六,结果为 d
    System.out.println(Integer.toString(13, 5));    // 十转五,结果为 23
    System.out.println(Integer.valueOf("23", 5));   // 五转十,结果为 13
    

    位数补齐

    如 int 类型数据长度是 32 位,输出时前面的“0”会被省略,想补齐可以使用 commons-lang3-*.jar 的 StringUtils 工具类对输出的字符串位数补齐。示例:

    String fullStr = StringUtils.leftPad("1010", 32, "0"); // 对二进制 1010 位数补齐
    System.out.println(fullStr); // 输出结果:00000000000000000000000000001010
    

    Java 中进制前缀

    Java 对二进制、八进制、十六进制提供了字面量前缀的表现形式,可以直接使用这几种进制形式计算或赋值,例如:

    // 都表示十进制的 10
    int i1 = 10; 	 // 十进制,没有前缀
    int i2 = 0b1010; // 二进制,前缀   0b(Java 7 或更高版本)
    int i3 = 012; 	 // 八进制,前缀   0
    int i4 = 0xA; 	 // 十六进制,前缀 0x
    
    System.out.println(i2 + 1);
    System.out.println(i3 + 1);
    System.out.println(i4 + 1);
    

    位运算

    位运算是在程序中对二进制数的一元和二元运算操作,其运算符有 & ,| ,~ ,^ ,<<, >>, >>> ,接下来我们来逐个说明:

    & (按位与)

    按位“与”操作处理两个长度相同的二进制数,两个相应的二进位都为 1,该位的结果值才为 1,否则为 0。
    如图:10 & 3 (int 类型长度 32 位,下图补位的 0 已被省略)。

    04

    | (按位或)

    按位“或”操作处理两个长度相同的二进制数,两个相应的二进位只要有一个为 1,该位的结果值就为 1。
    例如:10 | 3 (int 类型长度 32 位,下图补位的 0 已被省略)。

    ~ (按位非)

    该操作符是一元运算符,对一个二进制数的每一位执行逻辑反操作,使相应的位 1 变为 0,0变为 1。
    例如:~ 3

    06

    ^ (按位异或)

    按位“异或”操作处理两个等长的二进制数,如果某对应位值不同则结果值为 1,否则为 0。
    例如:10 ^ 3 (int 类型长度 32 位,下图补位的 0 已被省略)。

    07

    << (左移)

    向左进行移位操作,高位丢弃,移位后空缺的低位补 0。
    例如:10 << 2

    08

    >> (右移)

    向右移位,移位后空缺高位补 0,若为负数,高补 1。
    例如:10 >> 2 和 -10 >> 2

    09-1

    09-2

    >>> (无符号右移)

    向右移位,无论正负,高位空缺部分补 0。
    例如:10 >>> 2 和 -10 >>> 2

    10-1

    10-2

    应用示例

    1. m * 2^n 或 m / 2^n
    System.out.println(2 << 1); // 2 * 2^1 = 4 ,相当于乘以 2^1
    System.out.println(3 >> 1); // 3 / 2^1 = 1 , 相当于除以 2^1 向下取整
    
    1. 奇数偶数判断
    if(m & 1 == 1) {
        System.out.println("奇数");
    } else {
        System.out.println("偶数");
    }
    
    1. 随机概率
    // lr = 随机数;
    if ((lr & 0x3) == 0) {
        // 有 1/4 的概率执行该代码块
    }
    

    本文连接:https://www.cnblogs.com/newobjectcc/p/14438707.html



    参考:
    维基百科 - https://zh.wikipedia.org

    作者:陆十三
    转载请在明显位置注明出处!
  • 相关阅读:
    【BZOJ-4422】Cow Confinement 线段树 + 扫描线 + 差分 (优化DP)
    【BZOJ-2521】最小生成树 最小割
    mtools使用-1
    关于nodejs DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect.
    学习RUNOOB.COM进度二
    学习RUNOOB.COM进度一
    深入了解jQuery Mobile-3装载器
    深入了解jQuery Mobile-1
    mongodb的学习之旅一
    支付回调内容
  • 原文地址:https://www.cnblogs.com/newobjectcc/p/14438707.html
Copyright © 2011-2022 走看看