public class 原码反码补码 { public static void main(String[] args) { // 计算机中都是以补码形式运算的 /** * [+3] = [00000000 00000000 00000000 000000011]原 * = [00000000 00000000 00000000 000000011]反 * = [00000000 00000000 00000000 000000011]补 * [-3] = [10000000 00000000 00000000 000000011]原 * = [11111111 11111111 11111111 111111100]反 * = [11111111 11111111 11111111 111111101]补 */ System.out.println(Integer.toBinaryString(3)); System.out.println(Integer.toBinaryString(-3));// 11111111111111111111111111111101 /** * * * 2、真值 * * 因为第一位是符号位,所以机器数的形式值就不等于真正的数值。 * 例如上面的有符号数 10000011,其最高位1代表负, * 其真正数值是 -3 而不是形式值131(10000011转换成十进制等于131)。 * * 所以,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值。 * 例:0000 0001的真值 = +000 0001 = +1,1000 0001的真值 = –000 0001 = –1 */ /** * * 可见原码, 反码和补码是完全不同的. 为何还会有反码和补码呢? * * 首先, 因为人脑可以知道第一位是符号位, 在计算的时候我们会根据符号位, 选择对真值区域的加减. (真值的概念在本文最开头). * * 但是对于计算机, 加减乘数已经是最基础的运算, 要设计的尽量简单. 计算机辨别"符号位"显然会让计算机的基础电路设计变得十分复杂! * 于是人们想出了将符号位也参与运算的方法. * * 根据运算法则减去一个正数等于加上一个负数, 即: 1-1 = 1 + (-1) = 0 , 所以机器可以只有加法而没有减法, 这样计算机运算的设计就更简单了. * * 于是人们开始探索 将符号位参与运算, 并且只保留加法的方法. 首先来看原码: * * 计算十进制的表达式: 1-1=0 * * 为了解决原码做减法的问题, 出现了反码: * * 1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0 * * 发现用反码计算减法, 结果的真值部分是正确的. 而唯一的问题其实就出现在"0"这个特殊的数值上. 虽然人们理解上+0和-0是一样的, * 但是0带符号是没有任何意义的. 而且会有[0000 0000]原和[1000 0000]原两个编码表示0. * * 于是补码的出现, 解决了0的符号以及两个编码的问题: * * 8位:1-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 * = [0000 0001]补 + [1111 1111]补 // 符号位也参与计算的 * = [0000 0000]补 * =[0000 0000]原 * * 这样0用[0000 0000]表示, 而以前出现问题的-0则不存在了.而且可以用[1000 0000]表示-128: * * (-1) + (-127) = [1000 0001]原 + [1111 1111]原 = [1111 1111]补 + [1000 0001]补 = [1000 0000]补 * * -1-127的结果应该是-128, 在用补码运算的结果中, [1000 0000]补 就是-128. 但是注意因为实际上是使用以前的-0的补码来表示-128, * 所以-128并没有原码和反码表示.(对-128的补码表示[1000 0000]补算出来的原码是[0000 0000]原, 这是不正确的) * * 使用补码, 不仅仅修复了0的符号以及存在两个编码的问题, 而且还能够多表示一个最低数. 这就是为什么8位二进制, * 使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127]. * * 因为机器使用补码, 所以对于编程中常用到的32位int类型, 可以表示范围是: [-231, 231-1] 因为第一位表示的是符号位. * 而使用补码表示时又可以多保存一个最小值. * * 以上转载自:原码,补码和反码 - wqbin - 博客园 * 首先int型在当前的计算机中大多数是占了32位,这里我们为了简便,就当作8位来处理(注意最高位是符号位,也就是8位的数据表示范围是:-128~127), * 只需要满足比其大几位即可。因为计算机都是以补码计算的,计算的最后结果也是补码,计算机以补码保存。但是给用户还得转化为 * 原码(原码->反码->补码->反码->原码)。所以8位、6位、10位、32位.... 应该也是可以的。 * 8位: 1-1 见上面 * 12位:1-1 = 1 + (-1) = [0000 0000 0001]原 + [1000 0000 0001]原 * = [0000 0000 0001]补 + [1111 1111 1111]补 // 符号位也参与计算的 * = [0000 0000 0000]补 * = [0000 0000 0000]原 * 32位:计算也一样 */ /** * 取反~运算符 与 反码是不同的概念。 * 取反:针对补码的所有位取反操作,得到结果补码(计算机存储),最终转换为原码给用户。 * 反码:1. 针对原码的高位不变,其他位取反 * 2. 补码转反码,补码-1就是反码。 * * 计算机计算都是按补码计算的(也就是下算出补码再运算),存储也是补码结果。展示给用户就得转换成原码了。 * 补码转原码:补码高位为0 则补码=反码=原码 * 补码高位为1 则反码=补码-1,原码=反码除高位外,都取反 * 1 -> 2 -> 3 -> 4 -> 5 * 表达式 补码表达式 结果(补码) 反码 原码 原码(10) * 1 & 3 0001 & 0011 0001(高位0正) 0001 0001 1 * 1 & -3 0001 & 1101 0001(高位0正) 0001 0001 1 * 1 ^ 3 0001 ^ 0011 0010(高位0正) 0010 0010 2 * 1 | 3 0001 | 0011 0011(高位0正) 0011 0011 3 * ~1 先补码再运算得补码1110 1110(高位1负) 1101 1010 -2 * 针对计算结果(补码),如果为正数,只需要计算到第二步即可。因为正数 原码==反码==补码 * 如果为负数,则需要计算到第4步。 * */ ~127->00 0111 1111(原)->00 0111 1111(反)-> 00 0111 1111(补)->~之后为11 1000 0000(补)-> 11 0111 1111(反)->10 1000 0000(原) = -128 System.out.println(String.format("~15的结果为:%d", ~15)); // 按位取反的公式: ~n = -n-1 System.out.println(String.format("15的二进制:%s", Integer.toBinaryString(15))); // ~5 ~(-5) System.out.println(String.format("~5的结果为:%d", ~5)); // -6 System.out.println(String.format("~-5的结果为:%d", ~-5)); // 4 System.out.println(String.format("1&-3的结果为:%d", 1&-3)); // 1 /** * https://docs.microsoft.com/zh-cn/cpp/c-language/signed-bitwise-operations?view=msvc-160 * 对带符号整数进行的按位运算的工作方式与对无符号整数进行的按位运算的工作方式相同。 例如,-16 & 99 可用二进制格式表示 * 转换成补码计算。最终结果也是补码,但是高位为0,则原码=补码 * 11111111 11110000 * & 00000000 01100011 * _________________ * 00000000 01100000 * * 按位“与”的结果为 96。 */
// byte 占8位,最高位为符号位。 /** * 128: 原码:00 1000 0000 * 反码:00 1000 0000 * 补码: 00 1000 0000 * 强转也是依据补码来计算的: * -- 下面的推算是无意义的,1000 0000 字节中的最小负数 -128 * 补码:1000 0000 * = -128 * * 反推: * -128: 原码:10 1000 0000 * 反码:11 0111 1111 * 补码:11 1000 0000 * 强转成byte后 * 补码: 1000 0000 */ byte b1 = (byte)128; System.out.println(b1); // -128 /** * 129: 原码: 00 1000 0001 * 反码: 00 1000 0001 * 补码: 00 1000 0001 * 强转也是依据补码来计算的: * 补码:1000 0001 * 反码:1000 0000 * 原码:1111 1111 * = -127 */ byte b2 = (byte)129; // 1000 0001 System.out.println(b2); // -127 /** * 200原码:00 1100 1000 * 反码:00 1100 1000 * 补码:00 1100 1000 * 强转也是依据补码来计算的: * 补码:1100 1000 * 反码:1100 0111 * 原码:1011 1000 * = -56 */ byte b3 = (byte)200; System.out.println(b3); // -56
} }
位运算的实际应用:
1. 2的指数的取模运算:
10 % 16 = 10 = 10 & (16 - 1)
2. 大于当前数的,最近的16的倍数:
(reqCapacity & ~15) + 16; // netty 中规范实际分配内存大小, 小与512byte的每次分配出实际大小的最近的16整数倍。
如:(3&~15)+ 16
~15 = [1111 0000]补
3&~15 = [0000 0011]补 & [1111 0000]补 = [0000 0000]补 = [0000 0000]原
3&~15 + 16 = 0 + 16 = 16;
如 (18&~15)+ 16
18&~15 = [0001 0010]补 & [1111 0000]补 = [0001 0000]补 = [0001 0000]原
18& ~15 + 16 = [0001 0000] + 16 = 16 + 16 = 32
总结:上述满足 y=2的指数倍 , x&~(y - 1) + y;
3. 小与8192 = pageSize
subpageOverflowMask = ~(pageSize - 1) = -8192;
// capacity < pageSize
boolean isTinyOrSmall(int normCapacity) {
return (normCapacity & subpageOverflowMask) == 0;
}
如: 8191 & subpageOverflowMask
-8192 = [1010 0000 0000 0000]原 = [1101 1111 1111 1111]反 = [1110 0000 0000 0000]补
8191 = [0001 1111 1111 1111]原 = 补码
8191 & -8192 = [0001 1111 1111 1111]补 & [1110 0000 0000 0000]补 =
[0000 0000 0000 0000] 原 = 0
如: 8193 & subpageOverflowMask
8193 = [0010 0000 0000 0001]原 = 补码
8193 & -8192 = [0010 0000 0000 0001]补 & [1110 0000 0000 0000]补 =
[0010 0000 0000 0000] 原 != 0
4. netty bitmap中的位运算PoolSubpage.allocate()
/** * r = 63时bitmap[0]= -1,表示0~63的elem已经分配完了,64就是下一个bitmap的开始 * 计算过程: * 1L << 63的二进制数: * 1111111111111111111111111111111110000000000000000000000000000000 最大long高位1 * 十进制为:-2147483648L * 使用8位二进制模拟:1111 0000 或者 1111 1100 或者 1111 1000 计算结果都一样 * bitmap[0]的二进制数: * 111111111111111111111111111111111111111111111111111111111111111,63个1。高位为0 * 十进制:9223372036854775807L * 使用8位二进制模拟:0111 1111 * [1111 0000]原 | [0111 111]原 = [1000 1111]反 | [0111 1111]反 = * [1001 0000]补 | [0111 1111]补 = * [1111 1111]补 = [1111 1110]反 = [1000 0001]原 = [-1]10 * */ bitmap[q] |= 1L << r;