十进制
123表示的1*(10^2) + 2*(10^1) + 3*(10^0),(10^2表示10的二次方)
二进制
正整数二进制表示
二进制 | 十进制 |
10 | 2 |
11 | 3 |
111 | 7 |
1010 | 10 |
负整数的二进制表示
二进制使用最高位表示符号位,用1表示负数,用0表示正数。
整数有四种类型,byte/short/int/long,分别占1/2/4/8个字节,即分别占8/16/32/64位,每种类型的符号位都是其最左边的一位。
补码表示就是在原码表示的基础上取反然后加1。
负数的二进制表示就是对应的正数的补码表示,比如说:
- -1:1的原码表示是00000001,取反是11111110,然后再加1,就是11111111。
- -2:2的原码表示是00000010,取反是11111101,然后再加1,就是11111110。
- -127:127的原码表示是01111111,取反是10000000,然后再加1,就是10000001。
采用这种方式是因为,只有这种方式,计算机才能实现正确的加减法。
十六进制
二进制写起来太长,为了简化写法,可以将四个二进制位简化为一个0到15的数,10到15用字符A到F表示,这种表示方法称为16进制,如下所示:
2进制 | 10进制 | 16进制 |
1010 | 10 | A |
1011 | 11 | B |
1100 | 12 | C |
1101 | 13 | D |
1110 | 14 | E |
1111 | 15 | F |
可以用16进制直接写常量数字,在数字前面加0x即可。比如10进制的123,用16进制表示是0x7B,即123 = 7*16+11。给整数赋值或者进行运算的时候,都可以直接使用16进制,比如:
int a = 0x7B;
查看整数的二进制和十六进制表示
在Java中,可以方便的使用Integer和Long的方法查看整数的二进制和十六进制表示,例如:
int a = 25; System.out.println(Integer.toBinaryString(a)); //二进制 System.out.println(Integer.toHexString(a)); //十六进制 System.out.println(Long.toBinaryString(a)); //二进制 System.out.println(Long.toHexString(a)); //十六进制
位运算
位运算是将数据看做二进制,进行位级别的操作。位运算有移位运算和逻辑运算。
移位运算
移位有:
- 左移:操作符为<<,向左移动,右边的低位补0,高位的就舍弃掉了,将二进制看做整数,左移1位就相当于乘以2。
- 无符号右移:操作符为>>>,向右移动,右边的舍弃掉,左边补0。
- 有符号右移:操作符为>>,向右移动,右边的舍弃掉,左边补什么取决于原来最高位是什么,原来是1就补1,原来是0就补0,将二进制看做整数,右移1位相当于除以2。
int a = 4; // 100
a = a >> 2; // 001,等于1
a = a << 3 // 1000,变为8
逻辑运算
逻辑运算有:
- 按位与 &:两位都为1才为1
- 按位或 |:只要有一位为1,就为1
- 按位取反 ~: 1变为0,0变为1
- 按位异或 ^ :相异为真,相同为假
int a = ...;
a = a & 0x1 // 返回0或1,就是a最右边一位的值。
a = a | 0x1 //不管a原来最右边一位是什么,都将设为1
小数计算结果不精确
很多数,十进制也是不能精确表示的,比如1/3, 保留三位小数的话,十进制表示是0.333,但无论后面保留多少位小数,都是不精确的,用0.333进行运算,比如乘以3,期望结果是1,但实际上却是0.999。
二进制是类似的,但二进制只能表示哪些可以表述为2的多少次方和的数,来看下2的次方的一些例子:
2的次方 | 十进制 |
2^(-1) | 0.5 |
2^(-2) | 0.25 |
2^(-3) | 0.125 |
2^(-4) | 0.0625 |
计算机是用一种二进制格式存储小数的,这个二进制格式不能精确表示0.1,它只能表示一个非常接近0.1但又不等于0.1的一个数。数字都不能精确表示,在不精确数字上的运算结果不精确也就不足为奇了。
解决办法
大部分情况下,我们不需要那么高的精度,可以四舍五入,或者在输出的时候只保留固定个数的小数位。
如果真的需要比较高的精度,一种方法是将小数转化为整数进行运算,运算结束后再转化为小数,另外的方法一般是使用十进制的数据类型,这个没有统一的规范,在Java中是BigDecimal,运算更准确,但效率比较低。