通过前两章的学习,相信都对整数以及内存表示有了清晰的认知,这篇我们来谈谈除整数以外的数字。
1,浮点数:
python不像C/C++或者Java那样,把浮点数分为单精度和双精度,在python中浮点定义很简单,就是带小数点的数就是浮点数,而且浮点的实现方式和C语言双精度是一样的。
看如下代码:
a = 1.
b = 1.45
c = 3.4e-10
d = 4e6
e = 4.0e+5
print("a={0},b={1},c={2},d={3},e={4}".format(
a,b,c,d,e))
#输出:a=1.0,b=1.45,c=3.4e-10,d=4000000.0,e=400000.0
这段代码很好的说明了浮点数的书写形式,a说明如果数字后面带个点,即便是点后面没有别的数字了,这也是个浮点数,c,d,e都是采用科学计数的方式,通过打印的d的值可以看出,即便书写没有小数,但是打印的值还是带了小数,这说明只要是采用科学技术法,这个数就是浮点数。观察这个c很奇怪,它的打印形式和书写形式一模一样,这是和python的打印方式有关,如果这个小数位数后面超过3个0,那么就会用科学计数法打印出来。例如c=3.4e-4则会打印0.00034,如果是c=3.4e-5,则打印3.4e-5,这是个很有趣的细节。
看一下下面这个代码片段:
a = 0.1 + 0.1 + 0.1 - 0.3
print("a={}".format(a))#a=5.551115123125783e-17
是不是对结果有点惊讶,按照我们的思路,应还是0.0才对。实际上,这说明浮点数在内存中的表示是不精确的,所以才会出现这种似是而非的情况,所以我们在对浮点数计算的时候要相当小心,尤其是两个相同的值相减的情况,结果可能不是0。
说到这里,就引出了我们接下来要说的两个类型,分数和小数。
2,分数:
相信大家都不是九年制义务教育漏网之鱼,说到分数,那肯定是耳熟能详。但是在代码的世界,分数和我们想象的可能有点不同。看下下面这段代码:
a = 1/3
这个是分数么?看着很像,其实1/3只是个表达式,在python3.0之后,这个值表示真除,结果是0.3333333333333333,这还是个浮点数。其实要书写分数,没那么简单,我们需要引用一个模块franctions,看如下代码:
from fractions import Fraction
a = Fraction(1, 3)
b = Fraction(2, 6)
c = a + b
d = c + a
print("a={0},b={1},c={2},d={3}".format(a, b, c, d))
#结果:a=1/3,b=1/3,c=2/3,d=1
这段代码引入了一个Fraction类,这个类需要两个参数,第一个是分子,第二个是分母,我们看打印出的结果是不是和我们想象的一样?我们发现分数类型可以相加,其实加减乘除也都可以,这是因为python对操作符进行了重载,后续的学习中,我们会学习如何重载操作符。学过Java的人都知道,java不允许用户重载操作符,但是学过C++对这个并不陌生,因为C++也允许重载操作符。
另外,Fraction还支持字符串浮点数作为参数,例如Fraction('.25'),会打印出1/4。分数精确表示数固然很有意义,但是在实际运用的过程中,像0.1 + 0.1 + 0.1 - 0.3 这种事情会真的发生,而且用分子和分母表示数可能只是在数学计算中,现实中我们可能更喜欢直接用0.1这样的形式,例如这个蔬菜0.3元一斤,我们有三个0.1元,最后是不是要找钱,计算机就会根据这个公式0.1 + 0.1 + 0.1 - 0.3来计算需不需要找零,如果按照浮点数计算,得到非零的值,这显然是不合适的。这就引出了我们另外一个类型,小数。
3,小数
小数也需要引用python的一个模块decimal,我们知道到浮点数的精度不是固定的,所以才会导致0.1 + 0.1 + 0.1 - 0.3 结果不为零的情况,而小数的精度是固定的,所以计算不会出现我们意料之外的情况。看一下下面这段代码:
from decimal import Decimal
a = Decimal("0.1") + Decimal("0.1") + Decimal("0.1") - Decimal("0.3")
print("a = {}".format(a))
#结果:print("a = {}".format(a))
首先我们用decimal引入Decimal类,这个类需要一个字符串作为参数,我们看最后的打印结果是0.0,这是因为上面的表达式都是保留一位小数,这个精度是1,计算的结果精度也是1,所以我们得到了我们想要的结果。 但是Decimal的精度会根据情况扩展,如下面代码所示:
from decimal import Decimal
a = Decimal("0.10") + Decimal("0.1") + Decimal("0.1") - Decimal("0.3")
print("a = {}".format(a))
#结果:a = 0.00
精度变成了2,这说明小数精度会扩展成精度最大的那个小数的精度。
再看下面这个情况:
from decimal import Decimal
a = Decimal("1") / Decimal("7")
print("a = {}".format(a))
#结果:a = 0.1428571428571428571428571429
这一看是不是有点摸不着头脑,这其实也合理,因为1/7本身就是除不尽的,上面说小数的精度会扩展,因为除不尽,所以最后实际上是表示了最大的精度,但是这个和浮点数的非固定精度还是不同的,看如下代码:
from decimal import Decimal
a = Decimal("1") / Decimal("7") + Decimal("1") / Decimal("7") + Decimal("1") / Decimal("7") - \
Decimal("1") / Decimal("7") - Decimal("1") / Decimal("7") - Decimal("1") / Decimal("7")
b = 0.1 + 0.1 + 0.1 - 0.1 - 0.1 - 0.1
print("a = {0},b = {1}".format(a, b))
#结果:a = 0E-28,b = 2.7755575615628914e-17
a的结果是0E-28,这个是不是有点熟悉,为什么是-28呢?上面的代码1/7的时候,我们看到的结果a = 0.1428571428571428571428571429,这个精度就是28位,所以根据上面我们说小数会扩展精度,所以这个结果也是28位的精度,虽然其结果就是0,而对于b,用浮点数来计算,发现是非0的数,这个再次验证小数的精度是固定的,而浮点数的精度不是固定的。
那么有时候我们不想要这么大的精度怎么办呢?看如下代码:
import decimal
from decimal import Decimal
a = Decimal("1")/Decimal("7")
decimal.getcontext().prec = 4
b = Decimal("1")/Decimal("7")
print("a = {0},b = {1}".format(a, b))
#结果:a = 0.1428571428571428571428571429,b = 0.1429
可以看出,将精度设置为4的时候,b保留了四位小数,而且是通过四舍五入的形式。有时候,我们只是对某段代码想自定义精度,出了这个代码片段又想恢复默认的精度,一种方法就是再把之前的精度设置为回去,通过之前的代码,知道默认的精度是28,所以可以将prec设置为28即可,但是有时候,我们不想这么麻烦,可以通过上下文管理器来做。如下面代码所示:
import decimal
from decimal import Decimal
with decimal.localcontext() as cx:
cx.prec = 4
b = Decimal("1")/Decimal("7")
a = Decimal("1")/Decimal("7")
print("a = {0},b = {1}".format(a, b))
这段代码我们只是稍稍的调整了一下顺序,将变量a的赋值放在了下面,我们通过with语句,使得精度的设置只在with的语句块中生效,这样a的精度还是默认的精度,关于with,这里不展开说了,后面我会专门说这个。
4,复数
复数其实就是一个实部+虚部,目前来看,还我还没有遇到到需要用复数运算的场景,这里就简单的介绍下,请看下面的代码:
a = 1 + 2j
b = a * a
print("a={0},b={1}".format(a, b))
#结果:a=(1+2j),b=(-3+4j)
上面代码定义了一个复数a,其中实部为1,虚部为2j,其中j是代表虚部的标志,b是a和a的乘积,这个和数学上的运算是一样的,这里就不再说了。
到这里我们就完成了浮点数,小数,分数,以及复数的介绍。这些类型包括前面所讲的类型之间是如何运算的呢,以及这些类型有什么关键字和类来表示呢?下一章我们再详细谈这个问题。