参考:
15. 浮点算术:争议和限制 — Python 3.12.0 文档
虚拟环境和包
对于虚拟环境的应用场景,教材上举了一个希望对不同的应用程序使用不同的基础Python版本的例子,两个不同的Python版本可以独立工作在自己的虚拟环境种,互不影响。我的理解是:Python的虚拟环境可以实现在一定程度上的独立,包括一定程度地隔离系统安装的python环境,使你可以工作在一个更加独立的Python环境中。在这个环境中,你可以安装其它的Python工具包,而不影响原有系统安装的Python环境。
创建虚拟环境
用于创建和管理虚拟环境的模块称为 venv。venv 通常会安装你可用的最新版本的 Python。如果您的系统上有多个版本的 Python,您可以通过运行 python3
或您想要的任何版本来选择特定的Python版本。这样,就可以实现教材提到的应用场景中的多版本的问题。
我的电脑是windows系统,创建虚拟环境需要使用windows自带的命令行窗口。
注意:不要使用下面图中所示的Python应用程序窗口,那样就直接进入系统安装的Python环境了,而我们的目的是创建一个新的Python环境(虚拟的)。
"cmd"打开windows的命令行窗口之后,进入我的Python工作目录,然后运行教程上的命令
python -m venv tutorial-env
就会发现在工作路径中,创建了一个新的目录tutorial-env,新创建的Python虚拟环境将位于这个新的目录中。
创建虚拟环境后,如果要使用,则需要激活它。Windows上使用命令
tutorial-env\Scripts\activate
然后再次输入“python”,就进入到这个Python虚拟环境中的Python交互窗口了。
(tutorial-env) C:\D\03_programTry\pyExec>python
Python 3.11.3 (tags/v3.11.3:f3909b8, Apr 4 2023, 23:49:59) [MSC v.1934 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import fibo
>>> fibo.fib(100)
0 1 1 2 3 5 8 13 21 34 55 89
注意:在《Python教程学习 (7)》中,我们使用windows系统提供的python交互窗口,然后使用os.chdir()函数修改Python工作目录的方式,可以直接访问我们自定义的工作目录(C:\D\03_programTry\pyExec)中的python模块(fibo.py)。这里,我们又学到了一种方式,即可以通过在我们的工作目录中创建虚拟Python环境的方式,然后同样可以直接访问工作目录中的自定义python模块。
退出python交互窗口,使用exit()或者ctrl+z。然后可以再通过deactivate命令撤销激活的虚拟环境
>>> exit()
(tutorial-env) C:\D\03_programTry\pyExec>
(tutorial-env) C:\D\03_programTry\pyExec>deactivate
C:\D\03_programTry\pyExec>
使用pip管理包
创建了Python虚拟环境后,我们可以在这个虚拟环境中安装自己所需的软件包。
pip管理包是用来下载安装其它python工具包用的,即“你可以使用一个名为 pip 的程序来安装、升级和移除软件包。”。教程上介绍了很多相关的命令,这里不一一介绍。
例如,在我们的Python虚拟环境中,下载安装numpy,过程如下。(需要先激活虚拟环境,即运行:tutorial-env\Scripts\activate)
(tutorial-env) C:\D\03_programTry\pyExec>python -m pip install numpy
Collecting numpy
Downloading numpy-1.26.2-cp311-cp311-win_amd64.whl (15.8 MB)
---------------------------------------- 15.8/15.8 MB 1.2 MB/s eta 0:00:00
Installing collected packages: numpy
Successfully installed numpy-1.26.2
[notice] A new release of pip available: 22.3.1 -> 23.3.1
[notice] To update, run: python.exe -m pip install --upgrade pip
(tutorial-env) C:\D\03_programTry\pyExec>
安装后,使用命令python -m pip list查看虚拟环境中的所有安装包
(tutorial-env) C:\D\03_programTry\pyExec>python -m pip list
Package Version
---------- -------
numpy 1.26.2
pip 22.3.1
setuptools 65.5.0
[notice] A new release of pip available: 22.3.1 -> 23.3.1
[notice] To update, run: python.exe -m pip install --upgrade pip
(tutorial-env) C:\D\03_programTry\pyExec>
可以看到我们新安装的numpy,并且我们已经可以直接使用numpy这个模块了
>>> import numpy as np
>>> rng = np.random.default_rng()
# Generate one random float uniformly distributed over the range [0, 1)
>>> rng.random()
0.5560407838046962
>>> # Generate an array of 10 numbers according to a unit Gaussian distribution
>>> rng.standard_normal(10)
array([ 0.36613577, -1.32314174, 0.72286389, 0.17762364, -1.67140253,
0.09653621, -0.03922037, -2.67516402, 0.74631307, 0.98305923])
教程上其它的命令,应该都不难理解,有兴趣可以一一尝试。
浮点算术:争议和限制
这一章主要是深入了解浮点数的精确度问题,这个问题实际上在各种语言中普遍存在。Python提供了一些方法应对这一问题。
浮点数在计算机硬件中是以基数为 2 (二进制) 的小数来表示的。 例如,十进制 小数 0.625
的值为 6/10 + 2/100 + 5/1000,而同样的 二进制 小数 0.101
的值为 1/2 + 0/4 + 1/8。 这两个小数具有相同的值,唯一的区别在于第一个写成了基数为 10 的小数形式,而第二个则写成的基数为 2 的小数形式。
但是,大多数的十进制小数都不能精确地表示为二进制小数。这导致在大多数情况下,你输入的十进制浮点数都只能近似地以二进制浮点数形式储存在计算机中。这也就造成了浮点数的精确度问题。
例如:无论你使用多少位以 2 为基数的数码,十进制的 0.1 都无法精确地表示为一个以 2 为基数的小数。 在以 2 为基数的情况下, 1/10 是一个无限循环小数:
0.0001100110011001100110011001100110011001100110011...
例如,由于这个 0.1 并非真正的 1/10,将三个 0.1 的值相加也无法恰好得到 0.3:
>>> 0.1 + 0.1 + 0.1 == 0.3
False
虽然这些数字无法精确表示其所要代表的实际值,但是可以使用 math.isclose() 函数来进行不精确的值比较
>>> import math
>>> math.isclose(0.1 + 0.1 + 0.1, 0.3)
True
对于需要精确十进制表示的使用场景,请尝试使用 decimal 模块,该模块实现了适合会计应用和高精度应用的十进制运算。
>>> from decimal import *
>>> Decimal('0.1') + Decimal('0.1') + Decimal('0.1') == Decimal('0.3')
True
>>> Decimal(0.1) + Decimal(0.1) + Decimal(0.1) == Decimal(0.3)
False
>>> Decimal(0.1)
Decimal('0.1000000000000000055511151231257827021181583404541015625')
注意:
- import的时候,不要直接写 import decimal,一定要用from decimal import *。因为Deciaml()实际上是decimal里面的一个模块。如果使用了import decimal,则例子中的Decimal要换成'decimal.Decimal'
- Decimal括号里的数字要加引号,否则还是和原来的二进制表示的结果一样,只是会将二进制表示的数,用十进制精确地表述出来而已
另一种形式的精确运算由 fractions 模块提供支持,该模块实现了基于有理数的算术运算(因此可以精确表示像 1/3 这样的数值)。
>>> from fractions import Fraction
>>> Fraction(16, -10)
Fraction(-8, 5)
>>> Fraction('3/7')
Fraction(3, 7)
>>> Fraction('-.125')
Fraction(-1, 8)
Python 还提供了一些工具可能在你 确实 想要知道一个浮点数的精确值的少数情况下提供帮助。 例如 float.as_integer_ratio() 方法会将浮点数值表示为一个分数,并且由于这个比值是精确的,它可以被用来无损地重建原始值:
>>> x = 3.14159
>>> x.as_integer_ratio()
(3537115888337719, 1125899906842624)
>>> x == 3537115888337719 / 1125899906842624
True
关于sum() 函数,教材上说:“它能够帮助减少求和过程中的精度损失”,但在我的电脑上这个用法是失败的。因此,个人觉得这个方法不靠谱。
>>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 == 1.0
False
>>> sum([0.1] * 10) == 1.0
False
但math.fsum()是可用的
>>> import math
>>> math.fsum([0.1] * 10) == 1.0
True
表示性错误
表示性错误 是指某些(其实是大多数)十进制小数无法以二进制(以 2 为基数的计数制)精确表示这一事实造成的错误。 这就是为什么 Python(或者 Perl、C、C++、Java、Fortran 以及许多其他语言)经常不会显示你所期待的精确十进制数值的主要原因。
0.1的精确表示的例子,有兴趣可以看看。
fractions 和 decimal 模块使得这样的计算更为容易:
>>> from decimal import Decimal
>>> from fractions import Fraction
>>> Fraction.from_float(0.1)
Fraction(3602879701896397, 36028797018963968)
>>> Fraction(0.1)
Fraction(3602879701896397, 36028797018963968)
>>> Fraction('0.1')
Fraction(1, 10)
>>>
>>> Decimal.from_float(0.1)
Decimal('0.1000000000000000055511151231257827021181583404541015625')
>>> Decimal(0.1)
Decimal('0.1000000000000000055511151231257827021181583404541015625')
>>> Decimal('0.1')
Decimal('0.1')
>>> (0.1).as_integer_ratio()
(3602879701896397, 36028797018963968)
说明
- Fraction.from_float()和Fraction()的效果是一样的,这点对Decimal也一样
- Fraction()和Decimal()中的数字,带引号和不带引号的意义是不一样的。