Encode¶
编码(encode)就是将各种形式的输入根据一定的编码系统转换为数字(码点),然后再将各种进制的数字转换为计算机可以识别的二进制
二进制数字(binary digit)简称 bit(比特,或者叫位),即 0 或 1,8 位称作 1 个字节
计算机诞生美国,最初只考虑到26个基本拉丁字母的大小写、阿拉伯数字和英式标点符号等现代英语环境的一些元素(即键盘上那些东西),使用128个整数即可完整表示,于是诞生了ASCII编码系统。
ASCII 字符表前32个字符都是控制字符,很多都源于遥远的电报时代
比如,当电传打字机打印完一行之后,需要用一个控制命令把打印头复位回打印纸的左边,然后再用另一个控制命令把打印头往下移动一行
这俩动作分别对应了两个控制字符(CR & LF),也就是所谓的回车和换行
后来计算机传入西欧,他们所特有的字符无法完全用ASCII表示,于是做了扩展(表格符号,计算符号,希腊字母,拉丁符号),统称EASCII
无论ASCII还是EASCII,使用8bit(1Byte)二进制即可表示,所以都属于单字节系统。
但是汉字有成千上万个,即使常用的也有2500个,所以至少需要两个字节来表示,于是中国在ASCII的基础上制定了GB2312编码(包含6763个汉字),后来又在此基础上创建了GBK(27484个汉字,同时还包含藏文蒙文维吾尔文等少数民族文字),除了中国外,还有其他国家也会存在这样的问题,比如日文,韩文等,每个国家都制定一套自己标准,不可避免的会出现冲突。
于是人们就想着把所有语言统一到一套编码中,当时做这件事的有两个团队,一个叫UCS,一个叫Unicode,当他们发现对方的存在时决定共同搞一套规范,名字就叫Unicode,俗称万国码/统一码。它也是兼容ASCII的。通常用U+xxx形式表示。需要被定义的符合有很多,所以它肯定不是一次性定义完的,比如7.0版本时已收录了10w+符号,那它是咋定义的呢:分区,每个区可以存放2的16次方个字符,被称为一个平面,目前共有17分区/平面。最先被定义的肯定是最常见的那些字符,它们被放在了第一个分区/平面,称作基本平面(缩写为BMP,从U+0000到U+FFFF)剩下的都叫辅助平面(SMP)
Unicode只规定了每个字符对应的码点(数字),而一个码点占用几个字节由Unicode的编码方式UTF规定,也叫字符集,比如UTF-32,规定四个字节表示一个码点,但这出现了一个问题,就是如果文本都是纯英文的话,那么就比直接用ASCII多4倍的存储空间。所以出现了边长型的UTF-8编码,它会根据不同码点大小占用1~6个字节,比如英语通常为1Byte,汉字为3Byte,很节省空间。一般在计算机内存中会统一使用Unicode,但在存储和传输的时候都会转换为UTF-8以节省空间和提高性能。
Python3.x默认使用UTF-8,但像Python2.x等编程语言比 Unicode 诞生要早,所以默认编码是ASCII,所以一般需要在源码开头声明编码格式
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
第一行. 告诉类Unix系统这是一个Python可执行程序,Windows系统会忽略这个注释
第二行. 告诉Python解释器按照UTF-8编码读取源代码,也可以写为:coding=utf-8
"""
另外还有UTF-16,结合了定长和变长的方式。基本平面的字符占用2个字节,辅助平面占用4个,就是说要么占用2个要么4个字节。其实UTF-16是之前的UCS-2的超集,后来取代了UCS-2,因为UCS-2有个问题就是一个字符如果是4字节的会被当成两个2字节的字符,而UTF-16为此做了一个巧妙的设计(辅助平面的字符被拆成了两个基本平面的字符表示)避免了这个问题。
由于JS诞生于(1995年)UCS-2(1990年)和UTF-16(1996年)之间,所以最初选用了USC-2编码方式(没有选用UTF-8是因为当时UTF-8还不成熟,而UCS-2在内存方面操作及使用效率更高),不过ES6已经改为默认UTF-16,不会再有4字节的问题。
进制转换¶
- 十进制(Decimal):人类有十个手指头,为了方便理解,通常使用十进制
- 二进制(Binary):计算机芯片内使用的是门电路,只能表示0和1两个状态,所以使用二进制
- 十六进制(Hexadecimal):在代码书写时,为了简短显示,一般会使用十六进制,1位16进制是4bit,2位16进制数字是1Bytes
- 八进制(Octal)
与十进制互转¶
- 10进制转任意进制:除基取余,直到商为0,余数倒序
bin(15) # 转成二进制 0b1111
oct(15) # 转成八进制 0o17
hex(15) # 转成十六进制 0xf
- 任意进制转10进制:系数*基数^权次幂(幂从0开始)
"""
十进制满10进1
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15...
1024 = 1*10^3+0*10^2+2*10^1+4*10^0 = 1*1000+0*100+2*10+4*1 = 1024
数位为: 10^(n-1), ..., 1000, 100, 10, 1
二进制满2进1
0,1,10,11,100,101,110,111...
0b110 = 1*2^2+1*2^1+0*2^0 = 1*4+1*2+0*1 = 6
数位为: 2^(n-1), ..., 8, 4, 2, 1
八进制满8进1
0,1,2,3,4,5,6,7,10,11,12,13,14,15...
0o711 = 7*8^2+1*8^1+1*8^0 = 7*64+1*8+1*1 = 457
数位为: 8^(n-1), ..., 512, 64, 8, 1
十六进制满16进1
0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,10,11...
0xABC = 10*16^2+11*16^1+12*16^0 = 10*256+11*16+12*1 = 2748
数位为: 16^(n-1), ..., 4096, 256, 16, 1
"""
int(0b1111) # 15
int(0o17) # 15
int(0xf) # 15
与二进制互转¶
- 2进制与8进制互转:3位一组,421码
oct(0b1101) # 0o15
bin(0o15) # 0b1101
- 2进制与16进制互转:4位一组,8421码
hex(0b1101) # 0xd
bin(0xd) # 0b1101
浮点数存储¶
浮点数类型在存储时需要把十进制转换为二进制数存储
指数部分通常由8位二进制表示,为了能够表示负指数和正指数,以及在0附近表示小数,采用偏移值的方式,具体来说,偏移值是在实际指数值上加上一个固定的偏移量,以便能够表示负指数,在 IEEE 754 标准中,单精度浮点数偏移值通常是 127,双精度是 1023。
比如 13.75
整数部分的 13,转为二进制(除2取余倒序法) 1101
小数部分的 0.75 转为二进制(乘2取整倒序法) 0.11
合起来为:1101.11
科学技术法表示为:1.10111x2^3
然后转换为单精度浮点数的形式
符号位:为正数,用 0 表示
整数部分(用偏移值表示):3+127=130,转为8位二进制 10000010
尾数部分(补充到23位):10111000000000000000000
合起来后就是:0 10000010 10111000000000000000000
如果浮点数的小数不是0或者5结尾,将无法准确的转为二进制
比如 0.1
转为二进制将变成一个无限循环小数:0.00011001100110011...
再加上尾数的有限位数,存储时取近似值,最终计算后转为十进制必将是不准确的
因此各种编程语言以及数据库中的浮点数类型计算都是不精确的,对精度要求较高的业务场景通常使用定点数DECIMAL将整数和小数部分拆分开来存储。