32_Java中的进制
Java的进制
一、进制基础知识
进制概述:
指进位制,是一种计数方法,也称为进位计数法或位值计数法
十进制:0, 1, 2, 3, 4, 5, 6, 7, 8, 9
R进制:由(0~(R-1))组成,并且每一位上的数据逢R进1
我们的计算机是由逻辑电路组成,逻辑电路通常只有两个状态:开关的接通与断开
这两种状态正好就可以使用”1“ 和 ”0“表示,也就是我们要讲解的组成二进制的数字符号
二进制:0, 1
虽然二进制更适用于计算机,但是二进制表示数据太长了
为了解决这个问题,八进制和十六进制就来了
八进制:0, 1, 2, 3, 4, 5, 6, 7
十六进制:0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F(为了节省占位:1015改为了AF(大小写均可)使其只占用一个字节)
举例:
二进制:10000000000
八进制:2000
十六进制:400
进制越大,表现形式越短
问题:
10到底是几?
在Java中:
二进制:以0b开头
八进制:以0开头
十六进制:以0x开头
十进制:默认使用
练习:在控制台输出下面的数据,看结果是多少
0b10000000000
02000
0x400
参考代码:
package com.itheima; /* 练习:在控制台输出下面的数据,看结果是多少 0b10000000000 02000 0x400 */ public class Demo01 { public static void main(String[] args){ //从输出结果来看都是1024,在控制台输出的数字默认都是十进制数字 System.out.println(0b10000000000); System.out.println(02000); System.out.println(0x400); } }
二、进制转换
1、常见的进制转换:
R进制到十进制的转换
规则:按权展开法(系数*基数的权次幂相加)
系数:每一位上的数
基数:R进制,基数就是R
权:从数值的右侧,以0开始编号,对应位上的编号就是该位上的权
举例:0b101010
0b101010 = 1*2^5 + 0*2^4 + 1*2^3 + 0*2^2 + 1*2^1 + 0*2^0 = 32 + 0 + 8 + 0 + 2 + 0 = 42 系数:对应的每一位数:1、0、1、0、1、0 基数:根据0b判断是二进制,R = 2 权:从右往左依次从0开始步长为1累加
十进制到R进制的转换
规则:重复相除法(除基取余,直到商为0,余数反着写到位数)
把42转换为二进制,每行为每一次做除法,上一步的商做下一步的被除数
十进制数据 | 基数 | 商 | 余数 |
---|---|---|---|
42 | 2 | 21 | 0 |
21 | 2 | 10 | 1 |
10 | 2 | 5 | 0 |
5 | 2 | 2 | 1 |
2 | 2 | 1 | 0 |
1 | 2 | 0 | 1 |
得到:
42 = 0b101010
2、进制的快速转换:
如何实现二进制和十进制之间的快速转换呢?
采用8421码就可以实现
8421码:
是BCD代码中最常用的一种
BCD:(Binary-Coded Decimal)二进制码十进制数,在这种方法中,每一位二进制值代码的1都是代表一个固定数值,把每一位的1代表的十进制数加起来得到的结果就是它所代表的十进制数码。
二进制: | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
---|---|---|---|---|---|---|---|---|
对应的十进制数值: | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
举例:
0b101010 = 42
0b1010101 = 85
十进制到二进制? 通过使用88减去表中能够包括的最大值,如 -64后剩余24,此位数为1;而后32不足补0;继续进行。
88 = 0b1011000
二进制到八进制如何转换?
思路1:将二进制转换为十进制,然后再把该十进制转换为八进制
思路2:三位组合法
把二进制的数据,从右开始,每三位一组和(即三位并一位),最左边不够的时候,补0
然后,分别计算对应的十进制数值
最后,从左往右,把每个十进制的数据组合起来,就是一个八进制数据
二进制:0b101010
拆分: 101 和 010
二进制 | 000 | 001 | 010 | 011 | 100 | 101 | 110 | 111 |
---|---|---|---|---|---|---|---|---|
十进制 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
计算得出:5 和 2
组合: 52
最后得出八进制: 052
反之将八进制转换为二进制是一位拆三位
如:052 -> 5 和 2
得到: 101 和 010
最后得出: 0b101010
二进制到十六进制如何转换?
四位组合法:
把二进制的数据,从右开始,每四位一组合,最左边不够的时候,补0
然后,分别计算对应的十进制数值
最后,从左往右,把每个十进制的数据组合起来,就是一个十六进制数据
二进制:0b101010
拆分: 0010 和 1010
二进制 | 0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 |
---|---|---|---|---|---|---|---|---|
十进制 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
二进制 | 1000 | 1001 | 1010 | 1011 | 1100 | 1101 | 1110 | 1111 |
十进制 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
得到: 2 和 10(A)
组合: 2A
十六进制: 0x2A
反之从十六进制到二进制一位拆四位
如:0x2A
拆分: 2 和 A
得到: 0010 和 1010
组合: 00101010
得到二进制:0b101010
3、Java内置的进制转换:
java.lang.Integer类中的静态方法:
public static String toBinaryString(int i):在基数2中返回整数参数的字符串表示形式为无符号整数
public static String toOctalString(int i):在基数8中返回整数参数的字符串表示形式为无符号整数
public static String toHexString(int i):返回整数参数的字符串表示形式,作为16位中的无符号整数
public static String toString(int i, int radix):返回由第二个参数指定的基数中的第一个参数的字符串表示形式
参考代码:
package com.itheima; /* java.lang.Integer类中的静态方法: public static String toBinaryString(int i):在基数2中返回整数参数的字符串表示形式为无符号整数 public static String toOctalString(int i):在基数8中返回整数参数的字符串表示形式为无符号整数 public static String toHexString(int i):返回整数参数的字符串表示形式,作为16位中的无符号整数 */ public class Demo02 { public static void main(String[] args){ //一、、十进制转换为:二、八、十六进制 //public static String toBinaryString(int i):在基数2中返回整数参数的字符串表示形式为无符号整数 System.out.println(Integer.toBinaryString(42)); //public static String toOctalString(int i):在基数8中返回整数参数的字符串表示形式为无符号整数 System.out.println(Integer.toOctalString(42)); //public static String toHexString(int i):返回整数参数的字符串表示形式,作为16位中的无符号整数 System.out.println(Integer.toHexString(42)); System.out.println("----------"); //二、、进制转换为以后一个参数为基数的进制 //public static String toString(int i, int radix):返回由第二个参数指定的基数中的第一个参数的字符串表示形式 System.out.println(Integer.toString(0b101010, 10)); System.out.println(Integer.toString(052, 10)); System.out.println(Integer.toString(0x2A, 10)); System.out.println(Integer.toString(0x2a, 10)); System.out.println("----------"); //使用第二个中使用的方法实现任意进制之间的转换 System.out.println(Integer.toString(0b101010, 8)); System.out.println(Integer.toString(0b101010, 16)); } }
三、有符号数据表示法
十进制的数据:
+表示正数
-表示负数
计算机中的数据:
0表示正数
1表示负数
而对于计算机识别的数据来说,0和1本身也表示数据值,那么我们怎么判断它是数值位还是符号位呢?
规定:符号位位于数值第一位
比如说:二进制数0b101010。用一个字节来存储(注意:1字节 = 8位)
0010 1010 (缺位补0,首位0表示正数)
那么我们如何表示:-0b101010,用一个字节来存储数据呢?
1010 1010 (缺位补0,首位1表示负数)
原码表示法:是最简单的及其数表示法。用最高位表示符号位,1表示负号,0表示正号。其余位标识数值大小。
把数据1到7和数据-1到-7用原码表示(用一个字节存储)
数据 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
正数 | 0 0000001 | 0 0000010 | 0 0000011 | 0 0000100 | 0 0000101 | 0 0000110 | 0 0000111 |
负数 | 1 0000001 | 1 0000010 | 1 0000011 | 1 0000100 | 1 0000101 | 1 0000110 | 1 0000111 |
计算:1 + 2, 1 - 2
1 + 2 = 00000001 + 00000010 = 00000011 = 3
1 - 2 = 1 + (-2) = 00000001 + 10000010 = 10000011 = -3 显然这个结果是错误的!!!
由此出现了反码表示法:
正数的反码和原码相同
负数的反码就是它的原码除符号位外,按位取反(即1变0,0变1)
把数据1到7和数据-1到-7用反码表示(用一个字节存储)
数据 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
正数 | 0 0000001 | 0 0000010 | 0 0000011 | 0 0000100 | 0 0000101 | 0 0000110 | 0 0000111 |
负数原码 | 1 0000001 | 1 0000010 | 1 0000011 | 1 0000100 | 1 0000101 | 1 0000110 | 1 0000111 |
负数反码 | 1 1111110 | 1 1111101 | 1 1111100 | 1 1111011 | 1 1111010 | 1 1111001 | 1 1111000 |
计算:1 - 2, -1 - 2
1 - 2 = 1 + (-2) = 00000001 + 11111110 = 11111110 = -1
-1 - 2 = (-1) + (-2) = 11111110 + 11111101 = 11111011(符号位进位舍弃)= -4 显然还是有问题
最后采用补码表示法:
正数的补码和原码相同
负数的补码等于反码+1
把数据1到7和数据-1到-7用补码表示(用一个字节存储)
数据 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
正数 | 0 0000001 | 0 0000010 | 0 0000011 | 0 0000100 | 0 0000101 | 0 0000110 | 0 0000111 |
负数原码 | 1 0000001 | 1 0000010 | 1 0000011 | 1 0000100 | 1 0000101 | 1 0000110 | 1 0000111 |
负数反码 | 1 1111110 | 1 1111101 | 1 1111100 | 1 1111011 | 1 1111010 | 1 1111001 | 1 1111000 |
负数补码 | 1 1111111 | 1 1111110 | 1 1111101 | 1 1111100 | 1 1111011 | 1 1111010 | 1 1111001 |
计算: 1 - 2,-1-2
1 - 2 = 1 + (-2) = 00000001 + 11111110 = 11111111 = -1 (结果正确)
-1 - 2 = (-1) + (-2) = 11111111 + 11111110 = 11111101(符号位进位舍弃) = -3 (结果正确)
四、整数强制转换之数据溢出
请问下列代码是否有问题?如果有问题,如何解决呢?解决完毕之后,结果是多少呢?
byte b = 130; (130是默认为整型数据)
byte b = (byte)130; (使用强制转换)
二进制: | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
---|---|---|---|---|---|---|---|---|
对应的十进制数值: | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
130 = 0b10000010 先转换位二进制数据,一字节八位
但是默认为整型int(4字节:最高位符号位,缺位补0)
130原码:0 0000000000000000000000010000010
130补码:0 0000000000000000000000010000010 先通过数据默认表示形式获取默认补码(计算机底层使用补码进行计算,但是以原码呈现)
进行强制转换(即由四字节变为,一字节剩余八位,且以补码形式)
补码:1000 0010 最高位为1,即负数
通过补码获得反码:1000 0001 即:在补码基础上减一
通过反码获得原码:1 111 1110 即:符号位不变,其余位按位求反
得到对应的十进制结果为:-126
显然是有问题的,尽管通过强制转换使得编译通过,但是结果与实际不符合。
练习:将130改为300
整型原码:0000 0000 0000 0000 0000 0001 0010 1100
整型补码:0000 0000 0000 0000 0000 0001 0010 1100
强制转换为字节:(补码)0 010 1100 此处最高位为0,是正数原、反、补相同
反码:00101100
原码:00101100
结果为:44
五、浮点数存储
前面所讲的进制转换都是整数部分,现在了解一下小数部分
十进制浮点数和二进制浮点数的转换:
二进制浮点数转换为十进制浮点数
十进制浮点数转换为二进制浮点数
二进制浮点数转换为十进制浮点数
规则:按权展开法(系数 * 基数的权次幂相加)
系数:每一位上的数
基数:对应的进制数
权:
整数部分:从小数点左侧,以0开始编号,对应位上的编号就是该位上的权
小数部分:从小数点右侧,以-1开始编号,对应位上的编号就是该位上的权
举例:把二进制浮点数110.101转换为十进制浮点数
可以先将其分为整数部分和小数部分
0b110 ---> 6
0b0.101 ---> 0.625
110.101 = 1 * 2^2 + 1 * 2^1 + 0 * 2^0 + 1 * 2^(-1) + 0 * 2^(-2) + 1 * 2^(-3)
= 4 + 2 + 0 + 0.5 + 0 + 0.125
= 6.625
即:6.625
十进制浮点数转换为二进制浮点数转换
规则:整数部分重复相除法,小数部分重复相乘法
整数部分重复相除法:除基取余,直到商为0,余数反转
小数部分重复相除法:乘基取整,直到小数为0或者达到指定精确度位,整数顺序排列
举例:把十进制浮点数6.625转换为二进制浮点数
整数部分:
十进制数 | 基数 | 商 | 余数 |
---|---|---|---|
6 | 2 | 3 | 0 |
3 | 2 | 1 | 1 |
1 | 2 | 0 | 1 |
小数部分:
十进制小数 | 基数 | 积 | 取整 |
---|---|---|---|
0.625 | 2 | 1.25 | 1 |
0.25 | 2 | 0.5 | 0 |
0.5 | 2 | 1.0 | 1 |
整数部分反转,小数部分顺序排列:
110.101
浮点数存储:
根据国际标准IEEE 754,任意一个二进制浮点数都可以表示为:V = (-1)^s * M * 2 ^ E
s表示符号位,当s = 0,V为正数;当s = 1,V为负数
M表示有效数字
E表示指数位
2表示此处使用的是二进制
举例:十进制的浮点数6.625,写成二进制是110.101,相当于1.10101 * 2 ^ 2(类似于十进制的科学计数法)
按照上面V的格式,可以得出 s = 0, M = 1.10101, E = 2
IEEE 754 对浮点数存储的规定:
对于32位的浮点数,也就是float类型的,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M
对于64为的浮点数,也就是double类型的,最高位的1位符号位s,接着的11位是指数E,剩下的52位为有效数字M
IEEE 754对有效数字M和指数E,还有一些特别的规定:
规范化表示的时候,M要写成1.xxxxxx的形式,其中xxxxxx表示小数部分
IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分
比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去
指数E,为一个无符号整数(unsigned int)。这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为
0~2047但是,我们直到,科学计数法中的E是可以出现负数的,所以IEEE 754规定,E的真实值必须再减去一个中间数,对于8位的
E,这个中间数是127;对于11位的E,这个中间数是1023
比如,2^10的E是10,所以保存成32位浮点数是,必须保存成10 + 127 = 137,即10001001
举例:浮点数6.625F的存储,F表示此为:float
二进制:110.101
规范化形式表示:1.10101000000000000000000 * 2 ^ 2 23位表示E缺位补0
符号:s 为 0
有效数字:M:10101000000000000000000 23位
指数E:2 + 127 = 129 = 10000001 8位
最终存储形式:
0 10000001 1010100000000000000000 ---> 对应 s E M
六、浮点数运算之结果有误
存储时采用:s E M 的形式。
以下是计算过程,在此过程中都只是在规范化的基础上进行计算,计算机最终会将计算结果的规范化表示形式以IEEE形式存储
请问下面程序的运行结果是多少?
System.out.println(2.0F - 1.5F);
System.out.println(2.0F - 1.3F);
System.out.println(2.0F - 1.4F);
结果是:
0.5
0.7
0.6
实际运行结果:
0.5
0.70000005
0.6
逐个分析:
System.out.println(2.0F - 1.5F);
十进制:2.0F
二进制:10.0
规范化表示:1.00000000000000000000000 * 2 ^ 1
十进制:1.5F
二进制:1.1
规范化表示:1.10000000000000000000000 * 2 ^ 0
计算:
1.00000000000000000000000 * 2 ^ 1
1.10000000000000000000000 * 2 ^ 0 ---> 0.1100000000000000000000 * 2 ^ 1
结果是:0.0100000000000000000000000 * 2 ^ 1
结果的规范化表示:1.00000000000000000000000 * 2 ^ -1
0.5F的规范化表示?
二进制:0.1
规范化表示:1.00000000000000000000000 * 2 ^ -1 (与计算结果相同)
System.out.println(2.0F - 1.3F);
十进制:2.0F
二进制:10.0
规范化表示:1.00000000000000000000000 * 2 ^ 1
十进制: 1.3F
二进制:1.01001 1001 1001 1001 1001 1001……
规范化表示:1.01001100110011001100110 * 2 ^ 0 (截取有效数字为23位)
计算:
1.00000000000000000000000 * 2 ^ 1
1.01001100110011001100110 * 2 ^ 0 ---> 0.101001100110011001100110 * 2 ^ 1
0.010110011001100110011010 * 2 ^ 1 (结果)
结果的规范化表示:1.01100110011001100110100 * 2 ^ -1
0.7F的规范化表示?
二进制:0.10110 0110 0110 0110 0110 0110......
规范化表示:1.01100110011001100110011 * 2 ^ -1 (与计算结果不符合,末尾三位不匹配)
System.out.println(2.0F - 1.4F);
十进制:2.0F
二进制:10.0
规范化表示:1.00000000000000000000000 * 2 ^ 1
十进制:1.4F
二进制:1.0110 0110 0110 0110 0110 0110 0110......
规范化表示:1.01100110011001100110011 * 2 ^ 0 有效数字截取23位
计算:
1.00000000000000000000000 * 2 ^ 1
1.01100110011001100110011 * 2 ^ 0 ---> 0.101100110011001100110011 * 2 ^ 1
0.010011001100110011001101* 2 ^ 1 (结果)
结果的规范化表示:1.00110011001100110011010 * 2 ^ -1
0.6F的规范化表示?
二进制:0.1001 1001 1001 1001 1001 1001 1001......
规范化表示:1.00110011001100110011010 * 2 ^ -1 (与计算结果一致)
注意(0舍1入):即当截取23位数时,第24位为0直接舍去,但是如果为1需要向前进一位
浮点数运算,在存储的运算过程中都可能会有精度的丢失,故出现的结果和我们以为的结果就会有出入
在实际开发中不太建议直接使用float 或者double做运算
那么,我们到底如何避免这种精度丢失问题呢?
使用java.math.BigDecimal中的方法即可
构造方法:BigDecimal(String val)
成员方法:public BigDecimal subract(BigDecimal subtrahend):减法操作
其他方法,参考文档
参考代码:
package com.itheima; import java.math.BigDecimal; /* 浮点数运算 */ public class Demo04 { public static void main(String[] args){ System.out.println(2.0F - 1.5F); System.out.println(2.0F - 1.3F); System.out.println(2.0F - 1.4F); BigDecimal bd1 = new BigDecimal("2.0"); BigDecimal bd2 = new BigDecimal("1.3"); System.out.println(bd1.subtract(bd2)); } }
本文作者:如此而已~~~
本文链接:https://www.cnblogs.com/fragmentary/p/17019865.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步