Java使用bit array实现二进制,十进制,十六进制值之间的转换
如果读者还对bit数组还不太了解,可以去看一下我的另一篇文章,那里对bit数组和位运算都做了解释:
http://blog.csdn.net/zimu666/article/details/8284906
二进制
二进制的表达非常简单,只有0和1两个值,又因为他在数字电路中非常容易实现和处理,所以现代计算机几乎全部使用二进制来做为基础运算方式.
二进制运算方式是逢2进1.一组二进制数的值是通过这一个bit位的值,与相应的位指数想乘的结果.
在十进制中表达一个5位数,他们的指数排列是这样的:
指数 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|
值 | 2 | 1 | 1 | 6 | 0 |
这个十进制值的结果就是2 * 10^4 + 1 * 10^3 + 1 * 10^2 + 6 *10^1 + 0 *10^0 = 21160
如果在数的后面添加一个0,这个数就在十进制中进了一位,也就是以前数的十倍
二进制的表现方式相同,在讨论二进制的时候,通常需要说明一下哪一位是最高位(most significant bit),哪一位是最低位(least significant bit).
大部分使用的都是最低位在二进制数的最右边.
指数 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
值 | 0 | 1 | 1 | 0 | 1 | 0 | 1 | 0 |
与十进制的运算原理一样: 1 * 2^6 + 1 * 2^5 +1 * 2^3 +1 * 2^1 = 64 + 8 + 2 = 74
同理,如果向这个数(指的是二进制数0110 1010)后面添加一个0,这个数也想当于进了一位,只不过进位后的数是以前数的两倍.
signed/unsigned
这里需要讨论一下signed(有符号)和unsgined(无符号)
什么是符号呢?
一个二进制数的最高位(一般情况下是最左边的一位)被称为符号位,这个位置上面的值(0或1)表示了这个二进制数是不是一个负数,例如
一个8-bit的二进制数,例子中会尽量使用byte格式的数,因为整数类型的太长了,不便于操作
1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
通过这个计算我们也可以看出来,一个byte可以容纳的最小数-为128,二进制形式
1000 0000
一个byte可以容纳数的最大值为127
0111 1111
如果将byte的最大数 127 与 1 相加 会发生什么?
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | |
+ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
= | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
这个时候有一个余数1进位到了第八位,byte值的最高位,而这个最高位会被当作负数处理,结果就变成了-128
这里可以顺便提一下著名的2038年现象.
在类unix操作系统中普遍使用一个32-bit的int值来表示时间,时间每加1秒,这个int数就会增加1.如果按照上面的例子来看,当除了最高位以外所有位都为1时,这个数就到了int可以容纳的最大值(Integer.MAX_VALUE,).时间如果再前进一秒(+1),这个数就变成了int的最小值
这个情况发生的时间就是在2038年1月19日的3点14分07秒.下一步可能会发生的情况有2说: 1是时间会回到1970年1月1日,2是时间会回到1901年12月13日,这两个结果是根据具体的解析方式得来的(要看unix具体是怎么处理负数).
如果最高位是无符号的呢?那127 + 1的结果 1000 000 就可以被当作正数来处理
在Java中,没有unsigned这种数据类型修饰,所以如果需要表示一个unsgined value,就必须要一个比他位数更大的其他数据类型来表示.
比如byte可以用short来表示,int可以用long来表示.
正负数之间的转化
一个比较熟悉二进制数的人可以很轻松松的做到将正十进制数转为二进制数,或者是将正负二进制数转成十进制,但是如果直接将一个负数转成二进制确没有那么容易,一般都会使用补码(2-complement).将一个二进制数进行取反操作,获得反码,然后增加1(补码),就可以获得这个数的相反值了.
比如我们想要获得-12在二进制的表示方法:
12 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
---|---|---|---|---|---|---|---|---|
取反 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 |
补码 + 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
-12 | 1 | 1 | 1 | 1 | 0 | 1 | 0 | 0 |
补码最大的优点体现在计算机内部运算的逻辑电路中.有了补码运算,在计算机内部进行加减法操作的时候不需要创建两个独立的运算逻辑分别对应加减法,只需要一个加法电路外加一个补码电路,就可以对所有的值进行加减运算.
十六进制
十六进制在在数学中来解释就是逢16进1,一般使用0-9的数字来代表0-9,A-F来代表10-15
比如十进制数32,在十六进制下的表示为20,十进制数13,在十六进制下为C
像java,c这样的语言为了区分十六进制和十进制数值,会在十六进制数的前面加上 0x,比如0x20是十进制的32,而不是十进制的20
十六进制数计算机领域的运用非常广泛,因为他可以非常简单的展现一个数的二进制写法
我们列出十六进制值和二进制值的表:
0 | 0000 |
1 | 0001 |
2 | 0010 |
3 | 0011 |
4 | 0100 |
5 | 0101 |
6 | 0110 |
7 | 0111 |
8 | 1000 |
9 | 1001 |
A | 1010 |
B | 1011 |
C | 1100 |
D | 1101 |
E | 1110 |
F | 1111 |
如果我们有一个32-bit的二进制值,想把他转换成十六进制,可以对照上面的表很轻松的将这个书写出来
0110 | 0100 | 1111 | 1111 | 1001 | 1011 | 1000 | 1010 |
6 | 4 | F | F | 9 | B | 8 | A |
相反我们也可以很方便的将一个十六进制的值解析为二进制值.这里就不做示范了
十进制
十进制相比大家都知道了,每天都在用.使用十进制的原因,大概与人有十根手指有关,否则为什么会有这么多古代独立文明使用十进制呢.
介绍完上面的概念,就可以观看我们的程序了
这里我还是刻意使用了以bit为单位的操作,为了是让大家看明白其中每一位是怎么解析的
package bitmap; public class BaseConverter extends Bitmap { public static final int BINARY = 0; public static final int DECIMAL = 1; public static final int HEXADECIMAL = 2; public static final int FLOAT = 3; //public static final int OCTAL = 3; unimplemented private final String hexRegex = "0x[\dABCDEFabcdef]{1,8}"; private final String binRegex = "[01]{1,32}"; private final String decRegex = "-?\d{1,10}"; //private finalt String octRegex = "[01234567]{1,11}"; unimplemented public BaseConverter(int value) { this(String.valueOf(value), DECIMAL); } /** * Support only values in range of an Integer. * Values larger than Integer.MAX_VALUE or less than Integer.MIN_Value * will lead to unwanted result. * @param value value in String format * @param format supported are : BINARY, DECIMAL and HEXADECIMAL */ public BaseConverter(String value, int format) { super(32); try { if (format == BINARY) setBinary(value); else if (format == DECIMAL) setDecimal(value); else if (format == HEXADECIMAL) setHexadecimal(value); else throw new IllegalArgumentException("Unsupportet numerical format"); } catch (IllegalArgumentException e) { System.out.println("Unable to parse data:"); System.out.println(e.getMessage()); } } private void setBinary(String value) throws IllegalArgumentException { if (!value.matches(binRegex)) throw new IllegalArgumentException("Data is not in binary format"); //since the value is inverted for (int i = value.length() - 1, j = 0; i >= 0; i--, j++) if (value.charAt(i) == "1") set(j); } private void setDecimal(String value) throws IllegalArgumentException { if (!value.matches(decRegex)) throw new IllegalArgumentException("Data is not in decimal format"); int bitOffset = 0; int integer = Integer.parseInt(value); if (integer < 0) {//signed int set(31); integer += 1 << 31;//integer overflow will clear bit 31 } while (integer > 0) { if ((integer & 1) == 1) set(bitOffset); integer = integer >> 1; bitOffset++; } } private void setHexadecimal(String value) throws IllegalArgumentException { if (!value.matches(hexRegex)) throw new IllegalArgumentException("Data is not in hexadecimal format"); int BCDOffset = 0; //4bits-unit //0x are ignored for (int i = value.length() - 1; i > 1; i--) { byte b = hexToByte(value.charAt(i)); for (int j = 0; j < 4; j++) if ((b & (1 << j)) >= 1) set(j + BCDOffset * 4); BCDOffset++; } } @Override public String toString() { return toBin(); } /** * String returned from super.toString is inverted with * most significant bit on the rightmost position. * We will have to make a reverse of it. * @return binary representation of bitmap as String */ public String toBin() { StringBuilder result = new StringBuilder(super.toString()); return result.reverse().toString(); } /** * Return decimal represenation of bitmap (signed int) * The leftmost bit(bit 31) is used as sign. * @return bitmap value in deciamal form(integer) */ public int toDec() { int result = 0; int mask = 1; for (int i = 0; i < size - 1; i++) {//skip bit 31 if (get(i)) result += mask; mask = mask << 1; } if (get(31))// check bit 31(signed) result -= 1 << 31; return result; } public int toInt() { return toDec(); } /** * Return decimal representaion of bitmap (unsigned int) * Type long is required because the leftmost bit will cause * a <Integer Overflow> with type int * @return unsigned long value of the bitmap */ public long toUnsignedLong() { long result = 0; long mask = 1; for (int i = 0; i < size; i++) { if (get(i)) result += (long) mask; mask = mask << 1; } return result; } /** * Return hexadecimal representation of bitmap * @return bitmap value in hexadecimal form */ public String toHex() { StringBuilder result = new StringBuilder("0x00000000"); //initialize stringbuilder byte value = 0; int bitOffset = 0; int resIndex = result.length() - 1;//index of result starting at rightmost position for (int i = 0; i < size; i++) { if (get(i)) value += 1 << bitOffset; bitOffset++; if (bitOffset == 4) { result.setCharAt(resIndex--, getChar(value)); value = 0; bitOffset = 0; } } return result.toString().replaceAll("0x[0]{1,7}", "0x");//clear all unnecessary zeros } private byte hexToByte(char chr) { chr = Character.toUpperCase(chr); if (chr < 57) //chr is less than 10 in ascii-code return (byte) (chr - 48); return (byte) (chr - 55); //"A" - 55 = 10; } private char getChar(byte value) { if (value < 10) return (char) (value + 48); // 0 starts at 48 return (char) (value + 55); // "A"(10) starts at 65 } }
欢迎点评!