★本文是书稿中的一部分,主要介绍了 Python 中进制转换的实现方法。更多内容请参阅 www.itdiffer.com 我的个人网站 ”
3.4 进制转换
前面诸节所用到的整数、浮点数、分数,均是“十进制”的数,这符合数学和日常生产生活的多数习惯。而计算机则不然,它使用的是二进制(参阅第1章1.2节)。从数学角度看,用于实现记数方式的进位制除了十进制、二进制之外,还有八进制、十六进制、六十进制等。同一个数字,可以用不同的进位制表示。在数学和计算机原理的资料中,会找到如何用手工的方式实现各种进位制之间的转换——这些内容不在本书范畴,此处重点介绍使用 Python 内置函数实现进制转换,并由此观察一个貌似“ bug ”的现象。
3.4.1 转换函数
在 Python 内置函数中(如3.3节中的表3-3-1所示)提供了实现数值转换的函数,下面依次介绍。
1. 十进制转换为二进制
内置函数 bin()
能将十进制的整数转换为二进制,例如:
>>> bin(2)
'0b10'
>>> bin(255)
'0b11111111'
>>> bin(-3)
'-0b11'
bin()
只能对十进制的整数进行转换,所返回值是用字符串(参阅第4章4.2节)表示的二进制数字(简称“二进制字符串”),如图3-4-1所示,其中 0b
是二进制字符串前缀。
图3-4-1 返回结果组成
若将十进制的浮点数转化为二进制,是否可以用 bin()
?不能!官方文档中很明确地指出:Convert an integer number to a binary string prefixed with “0b”.(https://docs.python.org/3/library/functions.html#bin),还可以试试:
>>> bin(0.1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'float' object cannot be interpreted as an integer
不能使用 bin()
函数将十进制浮点数转化为二进制,但可以用别的方法实现,只不过此处暂不探讨,可以作为读者的一个思考题。
2. 十进制转换为八进制
内置函数 oct()
可以将整数转化为以 0o
为前缀的八进制字符串,如:
>>> oct(8)
'0o10'
>>> oct(256)
'0o400'
注意参数依然必须是整数。
3. 十进制转换为十六进制
内置函数 hex()
可以将整数转化为以 0x
为前缀的十六进制字符串,如:
>>> hex(16)
'0x10'
>>> hex(255)
'0xff'
在十六进制中,一般用数字
到
和字母
到
表示。在 hex()
返回的十六进制字符串中,所用的
到
的字母均为小写。
对于十进制的浮点数,虽然 hexo()
不能使用,但浮点数对象有一个方法可以实现向十六进制的转换。
>>> float.hex(0.1)
'0x1.999999999999ap-4'
>>> 0.1.hex()
'0x1.999999999999ap-4'
其实,这里得到的十六进制字符串与十进制浮点数 0.1
并非严格相等。
4. 二进制转换为十进制
如果在交互模式中直接输入二进制数,比如 01
,Python 解释器并不接受——所接受的是十进制数。
>>> 01
File "<stdin>", line 1
01
^
SyntaxError: leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers
当然,有的读者可能输入的是 11
,不会报错,但 Python 把它看做二进制 11
了吗?没有!它显然被 Python 认定为十进制的整数十一了。
>>> 11
11
>>> type(11)
<class 'int'>
但是,如果在交互模式中这样输入二进制数字:
代码语言:javascript复制>>> 0b11
3
>>> 0b10
2
>>> 0b11111111
255
注意,输入的不是“二进制字符串”,而是在二进制数前面写上了前缀 0b
,表示当前所输入的是二进制数,返回值则是对应的十进制整数。这种方式仅限于交互模式,在程序文件中不能这样做——千万不要将 >>> 0b11
复制到 .py
文件中。
更常用的方法是使用内置函数 int()
,在3.3.1节中提到过, int(x, base=10) -> integer
会在本节介绍,就是这里:
>>> int('0b10', 2)
2
>>> int('11111111', 2)
255
其中的'0b10'
和 '11111111'
都是二进制字符串,并且要设置参数 base=2
,即说明参数中的数字是二进制数字——不明确“告诉” Python ,它不知道 '0b10'
和 '11111111'
表示的是二进制字符串。
同样用 int()
函数,也能将八进制、十六进制的整数转换为十进制的整数。
>>> int('0xff', base=16)
255
>>> int('0o10', base=8)
8
3.4.2 不是 bug
在3.3.1节介绍内置函数 round()
时引用了官方文档的一个示例:
>>> round(2.675, 2)
2.67
该文档中明确说明:“This is not a bug”。那是什么呢?
类似的现象,其实还有,比如:
代码语言:javascript复制>>> 0.1 0.2
0.30000000000000004
>>> 0.1 0.1 0.1 - 0.3
5.551115123125783e-17
>>> 0.1 0.1 0.1 - 0.2
0.10000000000000003
这些计算中都有一些“误差”,也不是 Python 的 bug。
由于计算机是执行二进制计算的,要完成十进制数字的计算,不得不将十进制数字转化为二进制。对于十进制的整数而言,都有精确的二进制数对应。但是,对于浮点数就不完全有精确的二进制数对应了。
例如,
转化为二进制是:
。这个二进制数不精确等于十进制数
。同时,计算机存储的位数是有限制的,所以,就出现了上述“误差”。
这种问题不仅在 Python 中会遇到,在所有支持浮点数运算的编程语言中都会遇到,所以它不是 Python 的 bug 。
明白了原因后,该怎么解决呢?就 Python 的浮点数运算而言,大多数计算机每次计算误差不超过
。对于大多数任务来说,通过“四舍五入”(round()
函数,参阅3.3.1节)即能得到期望的结果。
但是,如果遇到“较真”的要求,怎么办?
代码语言:javascript复制>>> import decimal
>>> a = decimal.Decimal('0.1')
>>> b = decimal.Decimal('0.2')
>>> a b
Decimal('0.3')
>>> float(a b)
0.3
decimal
是 Python 标准库的一个模块,官方地址是 https://docs.python.org/3/library/decimal.html 。如上述代码示例,分别创建了与浮点数
和
对应的两个对象( decimal.Decimal
类型),它们之间相加,所得结果即为准确的
。但是这样计算的速度要低于浮点数运算。
decimal
模块不仅能解决上述计算“误差”问题,还有很多用途,比如某个业务要求所有计算结果必须有
位有效数字(注意与“四舍五入”不同),可以这样实现:
代码语言:javascript复制>>> from decimal import Decimal, getcontext
>>> getcontext().prec = 3
>>> Decimal(1) / Decimal(7)
Decimal('0.143')
>>> Decimal(3) * Decimal(math.pi) ** 2
Decimal('29.6')
此外,decimal.Decimal
对象亦有支持常见计算的方法。建议读者在学习完第8章和第9章,再认真阅读此模块的官方文档,并练习使用其中的各个方法。
★自学建议 编程语言中是基于计算机基本原理而存在的,因此,建议读者在学习本书之余——学有余力,阅读有关计算机基本原理的书籍,这样不仅对编程语言,乃至于以后的工作实际都会大有裨益。例如对于本节提到的“进制转换带来的计算误差”问题,要想“知其所以然”,就不得不求助于计算机基本原理的知识。 我在个人网站 www.itdiffer.com 和微信公众号【老齐教室】都会发布有关计算机原理的内容,读者可查阅参考。 ”