计算机的存储单位

计算机只能识别二进制数据,也就是0和1(0和1实际上对应的是高低电平,或者磁极方向等),对应0和1的最小存储单位是bit,bit是数据传输的最小单位。人们又规定特定位数的0和1组合在一起,表示特定的信息含义,例如ACSSII码就是通过8个bit存放的0和1来表示表示特定的字符。8个bit组成的数据存储单位称为byte,这是最小的数据存储单位。参考:https://cloud.tencent.com/developer/article/1353743

计算机存储单位 - 换算关系

bit (比特)(Binary Digits):存放一位二进制数,即 0 或 1,最小的存储单位。

1 byte(字节)= 8 bit

1KB = 210 bit = 1024 bit

1MB = 220 bit = 1024 KB

1GB = 230 bit = 1024 MB

1TB = 240 bit = 1024 GB

1PB …

1EB …

1ZB …

1YB …

1BB …

进制介绍和进制转换

进制也就是进位计数制,是人为定义的带进位的计数方法。 对于任何一种进制—X进制,就表示每一位置上的数运算时都是逢X进一位。 十进制是逢十进一,十六进制是逢十六进一,二进制就是逢二进一,以此类推,x进制就是逢x进位。百度百科

参考开度经验

二进制和十进制互转

10进制转2进制 以十进制数74示例

74 除以 2 商 37 余 0
37 除以 2 商 18 余 1
18 除以 2 商 9 余 0
9 除以 2 商 4 余 1
4 除以 2 商 2 余 0
2 除以 2 商 1 余 0
1 除以 2 商 0 余 1

按余数倒叙排列:1001010 ,实际存储的二进制位不是8的倍数(8bit是数据存储的最小单位),左边补0处理, 所以此处是 01001010

二进制转10进制

以 0010101 为示例
0 * 27 + 1 * 2 6 + 0 * 2 5 + 0 * 24 + 1 * 23 + 0 * 22 + 1 * 21 + 0 * 20 = 37

二进制、八进制、十进制互转

二进制数 01001010 对应十进制数 74 对应8进制数 112

二进制转八进制

方法:3位二进制数对应一位8进制数(从右到左开始转换,不足时补0)

1、 01001010 是8位二进制数,从右到左按3位进行转换,不足补0。

拆分后为 001 001 010

2、 拆分后的数据进行计算

001 0 * 22 + 0 * 21 + 1 * 20 = 1

001 0 * 22 + 0 * 21 + 1 * 20 = 1

010 0 * 22 + 1* 21 + 0* 20 = 2

3、计算后组合的8进制数为 112

八进制转二进制

方法: 八进制数通过除2取余法,得到二进制数,每个八进制数对应3个二进制位,不足时在最左边补零

1、 八进制数112 拆分。 1 1 2 。

2、 单独对每一个八进制数进行除2取余法 ,不足时左边补0,凑3位。

1

1 除以2 商 0 余 1 --> 001

1

1 除以2 商 0 余 1 --> 001

2

2 除以2 商 1 余 0

1 除以2 商 0 余 1 --> 10 --> 010

3、 拼接

001001010 --> 01001010

八进制转10进制

112 > 1* 8 2 + 1 * 8 1 + 2 * 8 0 = 74

10进制转八进制

1、 除8取余法 ,以10进制数 74示例

74 除以 8 商 9 余 2

9 除以 8 商 1 余 1

1 除以 8 商0 余 1

2、 倒叙排列得到八进制数: 112

16进制和二进制互转


16进制表述特殊说面,10进制数转16进制数时,使用除16取余法,余数最大为15,当余数大于9时,使用字母表示

0  1  2  3  4  5  6  7  8  9  10  11  12  13  14  15
0  1  2  3  4  5  6  7  8  9  A   B   C   D   E   F

10进制转16进制

以10进制数74为例
74 除以 16 商 4 余 10
4 除以 16 商 0 余 4
10用字母表示为A ,所以74转16进制为 4A

16进制转10进制

以16进制数4A为例
4 * 16 + 10 = 74

16进制转二进制

方法: 十六进制数通过除2取余法,得到二进制数,每个十六进制数对应4个二进制数,不足时在最左边补零
16进制数 4A
4转二进制为 100(具体转换参考上面8进制转换) , 补0之后为 0100
A 转 二进制为 1010,不用补0
拼接之后为 01001010

二进制转16进制

二进制数 01001010为例:
4位二进制数对应一位16进制数(从右到左开始转换,不足时补0)

0100 0 * 23 + 1 * 22 + 0 * 21 + 0 * 20 = 4

1010 1 * 23 + 0 * 22 + 1 * 21 + 0 * 20 = 10

拼接后16进制数为 4A

程序中对不同进制数的表示

java中对不同进制表示

int i = 0112; // 8进制 
        int j = 0x4A; // 16 进制 
        int k = 74;
        System.out.println(i==j);
        System.out.println(k==j);
        // 结果为 true true

c语言中printf函数输出不同兼职

int x = 74;
printf("%x", x);  // 以16进制格式输出
printf("%o", x);  // 以8进制格式输出
printf("%d", x);  // 以十进制格式输出

字符编码

编码发展历史请看:

简而言之,计算机通过二进制数据表示特定含义的信息,不同的编码标准,二进制数据对应不同的含义。

ASCII编码, 8个二进制位(8bit)表示一个特定字符。

GBK2312 16个二进制位表示一个字符

UTF-8使用1~4字节为每个字符编码:

1、一个US-ASCIl字符只需1字节编码(Unicode范围由U+0000~U+007F)。
2、带有变音符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文等字母则需要2字节编码(Unicode范围由U+0080~U+07FF)。
3、其他语言的字符(包括中日韩文字、东南亚文字、中东文字等)包含了大部分常用字,使用3字节编码。
4、其他极少使用的语言字符使用4字节编码。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4cESg7z3-1670329003476)(https://baike.baidu.com/pic/ASCII/309296/1/e850352ac65c103880a07b53bc119313b17e8941?fr=lemma&ct=single#aid=1&pic=e850352ac65c103880a07b53bc119313b17e8941)]

正数和负数二进制的存储方式

// 如果是一个负 整数,java中用二进制表示是先对正整数进行取反再加一
5 的二进制 00000101 取反 --> 11111010 加一 -->   11111011
// 如果是一个小数,因为小数存储有但进度和双进度之分,这里以单精度示例 
0.5 的浮点数为   00111111000000000000000000000000  
-0.5 的浮点数为  10111111000000000000000000000000 
// 第一位是符号位,你很容易看出来,这个就转变了一下正负而已    
    
// 代码验证
        System.out.println(Integer.toBinaryString(5));
        System.out.println(Integer.toBinaryString(-5));
        System.out.println(Integer.toBinaryString(Float.floatToIntBits(0.5f )));
        System.out.println(Integer.toBinaryString(Float.floatToIntBits(- 0.5f )));
// 输出
101
11111111111111111111111111111011
111111000000000000000000000000
10111111000000000000000000000000

上面的是总结性的描述,具体可以往下看

负整数二进制表示
正数的源码  --》 反码 -- 》 反码加1
以-28示例 
1、28用二进制表示为  00011100 
2、反码为 11100011
3、反码加1(逢二进一)表示为  11100100
用java代码进行验证
System.out.println(Integer.toBinaryString(-28));//由于int占4个字节,可以理解为对000000000000000000011100进行反码, 或者理解为左边补一
输出结果为11111111111111111111111111100100
    
java中byte类型的极限是-128~127
127 二进制表示为  01111111
-128  用上面的方法进行转码表示为  10000000  
这里左边第一位可以理解成符号位,但不是简单的表示正负

将byte按二进制输出:https://zhidao.baidu.com/question/1115031702245345779.html

二进制表示小数

例子:

0.23 * 2 整数部分为 0 小数部分为 0.46

0.46 * 2 整数部分为 0 小数部分为 0.92

0.92 * 2 整数部分为 1 小数部分为 0.84

0.84 * 2 整数部分为 1 小数部分为 0.64

0.64 * 2 整数部分为 1 小数部分为 0.24

0.24 * 2 整数部分为 0 小数部分为 0.48

0.48* 2 整数部分为 0 小数部分为 0.96

拼接之后应该为 0011100…

参考:

二进制表示小数部分精度问题

按照上述方法计算出的小数部分的二进制数可能是无限的,在实际存储中我们会根据精度对小数的二进制位进行截取。参考 IEEE 754标准

单精度浮点数占用4个字节(32位)存储空间来存储一个浮点数,包括符号位1位,指数位(阶码)8位,尾数23位。
双精度浮点数使用 8个字节(64位)存储空间来存储一个浮点数,包括符号位1位,指数位(阶码)11位,尾数52位。

计算公式

V = (-1)^s *(1+M)* 2^(E-127)(单精度)

V = (-1)^s *(1+M)* 2^(E-1023)(双精度)

s 为符号位,用于标识小数的正负
M为尾数位,尾数位的存储会舍去1, 所以需要加上1,具体看示例
E 表示阶码位的大小
// 示例1
public static void floatTest1(){
    /*
    *  单精度浮点数转二进制,以3490593.8进行示例
    *   1、 3490593 转二进制为  1101010100001100100001  0.8 转二进制位  1100.....省略,最终得到1101010100001100100001.1100
    *   2、 1101010100001100100001.1100 右移21位,1.1010101000011001000011100
    *   3、 由于单精度只有21位尾数,对小数点后的数据截取23位作为尾数(这里如果第24位是1,那么做进1处理),得到10101010000110010000111,小数点前的数据作为隐藏位舍去
    *   4、 2^(e-1)-1,e为指数位位数,此处e为8,偏移量为 127, 指数位存储为 127 + 21(右移21位,加21) 的二进制数   10010100
    *   5、 符号位0表示正,1表示负,此处为0
    *   6、 所以拼接得到的数为 01001010010101010000110010000111
    *   7、java中如下方法输出的二进制数据会忽略左边的0
    * */
    System.out.println(Integer.toBinaryString(3490593));
    System.out.println(Integer.toBinaryString(148));
    float f = 3490593.8f;
    System.out.println(f);
    System.out.println(Float.floatToIntBits(f));// 此处应该是将float对应的二进制数直接转十进制数
    System.out.println(Integer.toBinaryString(Float.floatToIntBits(f)));
}
// 输出结果 
1101010100001100100001
10010100
3490593.8
1247087751
1001010010101010000110010000111
示例二
public static void floatTest2(){
    /*
    * 精度损失问题,3490593 转二进制为  1101010100001100100001  0.9 转二进制位  1110.....省略,最终得到1101010100001100100001.1110
    * 舍去到24位的时候,最后面的1进行进1处理,所以得到尾数位为10101010000110010001000,最终输出为3490594.0
    * */
    System.out.println(Integer.toBinaryString(3490593));
    System.out.println(Integer.toBinaryString(148));
    float f = 3490593.9f;
    System.out.println(f);
    System.out.println(Float.floatToIntBits(f));
    System.out.println(Integer.toBinaryString(Float.floatToIntBits(f)));
}
// 输出结果 
1101010100001100100001
10010100
3490594.0
1247087752
1001010010101010000110010001000
示例三 0.5用单精度存储
1、 0.5转二进制为 1 
2、 正数位和小数位结合 表示为 0.1 ,左移一位, 1.0  
3、所以尾数位为 0,补齐21位,  000000000000000000000
4、127 + -1 (左移1位,减1)= 126, 转二进制为01111110
5、 符号位为 0 
7、 拼接后得到00111111000000000000000000000000

参考:

https://www.shikexu.com/archives/2085#respond

浮点数计算公式:https://zhuanlan.zhihu.com/p/161346478

计算机浮点数规格化表示:

负小数在二进制中表示
// 按照 IEEE 754标准 ,负小数和正小数只是在符号位上有不同,符号位 0 - 正,1 - 负
        System.out.println(Integer.toBinaryString(Float.floatToIntBits(0.5f )));
        System.out.println(Integer.toBinaryString(Float.floatToIntBits(- 0.5f )));
// 输出
111111000000000000000000000000
10111111000000000000000000000000
    // 这么看舒服点
    111111000000000000000000000000 -->    0 01111110 00000000000000000000000 
    10111111000000000000000000000000 -- > 1 01111110 00000000000000000000000

实数二进制加减

正整数相加不赘述,和十进制相加相同。

前置知识点

1、正数的原码 反码 补码完全相同。
2、负数的反码是将原码按位取反,补码=反码+1。
3、补码转原码和原码转补码的方法是一样的。

正整数二进制相减

第一中方法是和十进制减法相同,不过如果位数过长,我感觉不太友好 100 - 001 = 011 (简单),

10000000 - 00010101  (稍微复杂点的) 
转换
01111112  (128)
00010101   (21)
--------
01101011   (107)

这里介绍另一种方法

补码的方式对于计算机而言,好像意义是通过一种电路就可以加法和减法。

将减法转成加法即可,x-Y = x + (-y) = (X)补码 + (-y)补码

// 6 - 8  = 6+(-8) =  00000110 + 11111000 = 11111110 = -(00000001 + 00000001)  = -2 
        System.out.println(Integer.toBinaryString(6));
        System.out.println(Integer.toBinaryString(8));
        System.out.println(Integer.toBinaryString(-8));
        System.out.println(Integer.toBinaryString(6-8));
// 输出
110
1000
11111111111111111111111111111000
11111111111111111111111111111110

(整数和负数二进制加减)

浮点数加减

表示写完了之后部分概念还是有点迷糊,有兴趣的看下视频讲解,下面演示的浮点数加减过程可能是错误的。

视频(我没看):https://www.icourse163.org/course/0809HIT020B-1001527001

浮点数加减:

V = (-1)^s *(1+M)* 2^(E-127)(单精度)

V = (-1)^s *(1+M)* 2^(E-1023)(双精度)

s 为符号位,用于标识小数的正负
M为尾数位,尾数位的存储会舍去1, 所以需要加上1,具体看示例
E 表示阶码位的大小 
1、对阶 对阶的时候1.01 * 2^(-11) 和1.01 * 2^(-12) 向高阶位看齐, 1.01 * 2^(-12)变为 0.101 * 2 ^(-11) ,1.01 变为 0.101 理解为二进制右移
2、尾数相加 (尾数加减时都使用补码相加减,需要附带上符号位和隐藏位)
3、规格化  (右规: 尾数右移一位,阶码加一,1111.0000需要右移三位) ,(左规: 尾数左移一位,阶码减一) 规格化数据定义  1/2 <|M| <1
  --这里我不太明白
4、舍入  单精度 23位尾数,计算时第24位为1 ,尾数末尾加一,示例 00011 --》 00100 
5、溢出判断 (阶码判断是否溢出--也不太清楚)

--相关概念:
原码的加减法计算规则
原码的加减法计算规则
计算机中原码的加减法计算规则为:
符号位不参与计算,数值位进行加减。

① 在加法中分两种情况:

    第二操作数为正号,此时对第一操作数和第二操作数的数值位做加法。
    第二操作数为负号,此时对第一操作数和第二操作数的数值位做减法。

② 在减法中分两种情况:

    第二操作数为正号,对第一操作数和第二操作数的数值位做减法。
    第二操作数为负号,对第一操作数和第二操作数的数值位做加法。

在对数值位做加减法规则如下:

做加法时,符号位不参与计算,数值位相加。如果最高数值位产生了进位,则结果溢出。如果最高数值位没有产生
进位,则结果正确,计算结果的符号位取第一操作数的符号。

做减法时,符号位也不参与计算,第二操作数取补码,与第一操作数相加。此时又分两种情况,相加时如果最高
数值位产生了进位,说明数值位计算结果正确,符号位取第一操作数的符号;如果相加时最高数值位没有产生进位,则
需要对计算结果取绝对值,即对所得到的数值位求补(所有数值位求反后+1)作为计算结果,符号位取第一操作数的
符号的反。
浮点数相加
0.1 转单精度  
    0.1 转二进制 000110011001100110011001100110011..... 
    左移4位 --> 1.10011001100110011001100110011
    尾数为小数点后23位,第24位是1,进1处理: 10011001100110011001101 
    阶码为 127 -4 =123 对应二进制 1111011 补足8位 01111011
    符号位为 0
    0.1的单精度浮点数对应的是
    0 01111011 10011001100110011001101
1.0 转单精度  0 01111111 00000000000000000000000     符号位0 阶码 127 01111111 尾数 00000000000000000000000

1.0f + 0.1f 计算如下:    
1、对阶  取阶码绝对值,为4,阶码小的尾数右移4位,  保留两个阶码的最大值作为计算后阶码,为127,  阶码: 01111111       
2、尾数相加 最前面的0是符号位 0 表示正
0 1.00000000000000000000000(尾数补上隐藏位1)
0 0.000110011001100110011001101(尾数补上隐藏位1,右移4位(对阶))
---------------------------
0 1.000110011001100110011001101
1.00011001100110011001101(单精度保留23位,第24位是1,向前进1)(这个是舍入操作)
  00011001100110011001101(隐藏1,作为尾数)
3、尾数计算没有进位,所以 符号位相加 0 + 0 = 0    (简单点就这么理解吧)
4、规格化 由于相加后,小数点前刚好是1,不用左移或者右移,所以此处阶码还是对阶后的阶码 01111111      
5、所以计算后得到  0 01111111 00011001100110011001101     
    
        System.out.println(Integer.toBinaryString(Float.floatToIntBits(1.0f )));
        System.out.println(Integer.toBinaryString(Float.floatToIntBits(0.1f )));
        System.out.println(Integer.toBinaryString(Float.floatToIntBits( 1.0f + 0.1f)));    
// 输出
111111100000000000000000000000
111101110011001100110011001101
111111100011001100110011001101
浮点数相减
例1:0.5 - 0.4375  (单精度) 
减法转加法  0.5 +  (-0.4375)
0 01111110 00000000000000000000000   (0.5)
1 01111101 11000000000000000000000  (-0.4375)(正的0.4375 符号位为0)

1、对阶  01111101 = 125  01111110 126  对阶后为 01111110 ,阶差为 1 ,(-0.4375) 尾数向右移一位
2、尾数的补码相加 
11000000000000000000000 补上隐藏位1 --》111000000000000000000000  右移1位 --》0.111000000000000000000000 --负数的补码为取反(0变1,1变0) --》 1.000111111111111111111111  再加一 --》1.001000000000000000000000
 
 0 1.00000000000000000000000 (补上隐藏位,正数补码和原码相同)
 1 1.001000000000000000000000 ((-0.4375)补码)
------------------------------
10 0.001000000000000000000000
(相加时如果最高数值位产生了进位,说明数值位计算结果正确,符号位取第一操作数的符号)(查看上方原码减法计算)符号位为 0
3、规格化      
   尾数左移3位,1000000000000000000000, 阶码减3, 01111110  -- 》 01111011
4、最终得到    
0 01111011 00000000000000000000000
示例 2:0.5f - 0.525f 
减法转加法  0.5 +  (-0.525)    
0 01111110 00000000000000000000000   (0.5)
1 01111110 00001100110011001100110  (-0.525)(正的0.4375 符号位为0)   
 
1、对阶 无需操作
2、尾数加减    
0 1.00000000000000000000000
1 0.11110011001100110011010  (-0.525的补码)
------------------------------
1 1.11110011001100110011010    
按原码的加减法计算规则,计算没有产生进位,此处对数值位求补 0.00001100110011001100110,符号位取第一操作数取反,为 1   

3、规格化
 尾数左移 5位,阶码 -5 ,01111110 - 00000101 =   01111001 , 尾数位100110011001100110(不包括隐藏位) ,不足23位补5个0
4、最终结果为
1 01111001 10011001100110011000000