6  数值对象

6.1 Python数值对象概述

Python中的数值对象有4种类型:

  1. 整型(int),表示整数。
  2. 浮点型(float),表示小数,字面量有小数点,如1.3-0.0
  3. 布尔值(bool),是整型类的子类(是特殊的整型),只有TrueFalse两种取值。在数值计算中,True自动转换为1,False自动转换为0。可用bool类将对象转换为bool类型。0,空序列, None等被转换为False,其他被转换为True
  4. 复数(complex),用于表示复数,用jJ表示数学中定义的 i= \sqrt{-1},字面量形如2+3j2+3J。 编程中很少用复数类型。
注记注意:True和TRUE不一样

Python是大小写敏感的,要注意布尔值TrueFalse只有首字母大写,其他字母都是小写。不能写成TRUEFALSE

你可能感到疑惑:为什么要用两个类型来表示整数和浮点数?不能用一个数据类型,如“数值型”,来表示数值型对象吗?

简单的答案是,不能。要理解其背后的原因,需要了解计算机如何存储数据。

6.2 计算机如何存储数字

6.2.1 进制,进制转换

数可以用不同的进制表示。

我们在日常生活中使用的是十进制,逢十进一,这是从上古时期流传至今的文化遗存,根源于人类有十根手指的生物特性。 计算机通过电压的高、低来代表0、1,因此天然使用二进制。

相同数值可以用不同进制表示,即存在进制的转换。转换进制不改变数值的大小,只是改变了数值的表现形式。如二进制的 (10)_2 和十进制下的2表达的数具有相同的值,只是在不同的进制下,此数值具有不同的表现形式。类似于质量、长度的单位转换,1千克=2斤,这种单位的转换并不会改变物体的质量,只是对同一数值的不同表达。

计算机内部存储使用2进制,学习编程语言时还会接触到8进制,16进制。 多数现代编程语言(包括Python),通过在数字前添加前缀0b0o0x来表示2进制(binary)、八进制(oct)和16进制(hex)的整数字面量。在16进制中,用英文字母a ~ f或A ~ F来表示数字10 ~ 15。

表 6.1: 2进制、8进制、16进制整数表示使用的前缀。
进制 前缀 字面量示例
16进制 0x0X 0x0, 0x3e7
8进制 0o0O 0o1543
2进制 0b0B 0b1001
表 6.2: Python中的进制转换函数
函数 功能 输入 输出
bin 10进制转2进制 整数 0b前缀的字符串
oct 8进制转2进制 整数 0o前缀的字符串
hex 16进制转2进制 整数 0x前缀的字符串
int(x, base) 其他进制转10进制 x代表待转换的整数
base代表x的原始进制
整数
注记拓展:int(x, base)的用法

int(x, base)将进制为 base 的整数 x 转换为 10 进制整数。

  • base 是取值范围在 0,2~36 中的整数,默认为10
  • base 取值为 0 时,x 必须带前缀’0x’/‘0b’/‘0o’
  • x 的类型为字符串,形式是整数字面量
# 将3进制数10转换为10进制
print(int("10", 3))

# 将16进制数777转换为10进制
print(int("0x777", 0))
print(int("0x777", 16))
3
1911
1911

int也可以不带base参数,使用形式为int(x),将参数x转换为整数类型。x可以是整数,浮点数或表示10进制整数的字符串。

print(int(-1.1))
print(int("101"))

# Python输出整数对象时都以10进制形式输出
# 因此下面两个语句的输出相同
print(int(0x123))
print(0x123)
-1
101
291
291

6.2.2 计算机如何存储整数

计算机的最小存储单位是位(bit),一个位代表一个二进制数位(binary digit)。更常用存储单位是字节(byte),1字节=8位。

1个位只能表示2个不同的数(0,1)。据乘法原理,2个位能表示 2^2=4 个不同的数(00,01,10,11),1个字节能表示 2^8=256个不同的数,n个位能表示 2^n 个不同的数。通过建立二进制整数与十进制整数之间的映射关系,我们可以让使用二进制的计算机存储十进制整数。

出于计算效率的考虑,许多编程语言有多种整数类型,允许用户在特定场景下选用最适合的整数类型。如C语言表示整数的类型有4种(short,int,long,long long),存储一个整数最少用16位,最多用64位。

Python只有一种整数类型int。int类型对存储一个整数的空间不设上限,因此Python存储的整数大小理论上仅受限于可用内存。 这种设计增加了计算与存储的开销,但向用户隐藏了底层的复杂性,降低了学习门槛。

6.2.3 计算机如何存储小数

有两种方法表示小数:

  1. 定点表示法,如300.1。因小数点的位置固定而得名。

  2. 浮点表示法,如 3.001\times 10^2。因随着指数的变动,小数点的位置可以浮动而得名。浮点表示法有3要素:

    1. 符号(sign)。3.001\times 10^2 的符号是正号。
    2. 指数(exponent)。3.001\times 10^2 的指数为2,即10的次方数。
    3. 尾数(mantissa)。3.001\times 10^2 的尾数为 3.001,即乘号前面的数。

主流硬件与编程语言用IEEE754标准实现小数的存储与计算。由于此标准用浮点表示法表示小数,因此在编程语言中,表示小数的数据类型也称为浮点型(float)。

计算机的浮点数表示也采用二进制。如 (1.0101)_2\times 2^6,其尾数是二进制数 (1.0101)_2,指数运算的底数是2。2的幂次此处用10进制6表示,是为了方便读者阅读。

在实践中,计算机会用1位存储符号,若干位存储尾数,若干位存储指数。如IEEE754规定双精度浮点数(double类型)用1位存储符号,11位存储指数,52位存储尾数,存储一个浮点数合计消耗64位。 这里用一个详细例子展示了一个十进制浮点数如何被转换为二进制浮点数来存储。

Python的浮点数只有一种类型,采用前述的IEEE754中double标准。 double能表示的数的绝对值范围约为 10^{-308}\sim 10^{308},可实现15位有效数字1

注记拓展:特殊浮点数

float() 还可将下列特殊字符串转换为浮点数:

  1. “Inifinity”(正无穷)
  2. “-Infinity”(负无穷)
  3. “NaN”(非数值,如用 0 作除数得到的结果)

6.2.4 现象1:计算机也可能算“错”

同一个数字,在一种进制下可以用有限位数字精确表示,但在另一种进制下却可能是无限小数,如3进制下的1位小数 (0.1)_3,在10进制下等于 1\times 3^{-1} 是无限小数。

同理,一些10进制下的有限位小数,用2进制存储时是无限位小数。如0.1在2进制下是无限循环小数 (0.0\dot{0}\dot{0}\dot{1}\dot{1})_2。由于计算机存储资源有限,所以只能用比较接近的有限位小数来近似表示无限位小数,这就是浮点数的表示误差。

由于存在浮点数的表示误差,所以计算机在计算时会存在误差。 这些误差通常不影响实际应用,但有时可能会让你感到意外。

注记拓展:用math.close()比较浮点数是否相等
  • 由于浮点数存在表示误差,因此比较两个浮点数是否相等不宜使用运算符==,而应使用math模块的isclose()方法。
  • 如果要进行精确的10进制计算,可使用Python的decimal模块。
import math
# 判断0.1 + 0.2是否与0.3相等
print(0.1 + 0.2 == 0.3)

print(math.isclose(0.1 + 0.2, 0.3))
False
True
注记比喻:理解浮点数标准

IEEE 754 就像一张方格坐标纸。你可以快速、精确地在格子交点上做标记(可精确表示的浮点数),但如果你想标记一个不在交点上的点(如 0.1),就必须舍入到最近的格点。这张纸的格子有多密,就是“精度”;这张纸有多大,就是“表示范围”。IEEE 754 统一规定了格子的大小和整张纸的规格。

6.2.5 现象2:溢出错误

由于整数与小数的存储方式不同,Python能表示非常大的整数,而能表示的小数范围就相对有限。因此,如果试图将一个超出浮点数表示范围的整数转换为小数,就会产生溢出(overflow)错误。

i = 9999 ** 9999
float(i)
---------------------------------------------------------------------------
OverflowError                             Traceback (most recent call last)
Cell In[4], line 2
      1 i = 9999 ** 9999
----> 2 float(i)

OverflowError: int too large to convert to float

6.3 数值对象运算

数值对象支持的运算主要见于:

  1. 运算符:算术运算符,位运算符;
  2. 内置函数,如abs,round,pow,divmod;
  3. 其他int对象方法float对象方法
  4. math库(常用数学计算),random库(随机数生成);
  5. cmath库(复数类型对象的计算)。

6.3.1 数值对象适用的运算符

  1. 算数运算符,适用于int与float类型。
表 6.3: 算数运算符一览。
运算符 说明 优先级 结合性
** 从右至左
+X
-X
正号
负号
从左至右
*
/
//
%
乘法
除法
向下取整除法
求余数
同上
+, - 加减法 同上
  1. 位运算符,仅适用于int类型。

位运算是对整数的2进制表示进行的以位为单位的操作。编程基础课程很少使用位运算。

表 6.4: 位运算符一览,结合性都是从左至右
运算符 说明 优先级
~X 按位取反
<<, >> 左移, 右移
& 按位与
^ 按位异或
| 按位或
注记注意:区分位运算符、逻辑运算符

有的学习材料中用&|~表示逻辑运算【与】【或】【非】。

但在Python中,前述符号是位运算符,逻辑运算由关键字andornot实现。

注记练习

计算下列表达式的值:

  1. 15 // 4
  2. 7 / 2 + 7 // 2
  3. 7 % 2 + 7 // 2
  4. 2 ** 3 + 5 % 2
  5. 16 ** 0.5 ** 2

6.3.2 数值计算内置函数

表 6.5: 用于数值计算的内置函数
函数 含义 使用示例
abs 求绝对值 abs(10)
round 舍入到指定位数 round(100.123, 2)
divmod 做除法(division)
返回商与余数(modulo)
divmod(100, 17)
pow pow(x,y),返回 x ** y
pow(x, y, z),返回x ** y % z
pow(3, 2)
pow(3, 2, 2)
  1. round(n, d=0)将数值d精确到小数点后d位。d可以是负整数,d的默认值为0。其舍入规则是“四舍六入五取偶”,与用浮点数近似小数的舍入规则相同。 背后的思想是:0.5与1和0差距相同,如果统一为五舍或五入都会引入系统性的误差。在等距情况下舍入到最接近的偶数,可以避免系统性的偏大或偏小。
# 舍入到最近的偶数
print(round(1.5))
print(round(2.5))

# 由于浮点数的表示误差,round的结果有时看似并不遵循【五取偶】
print(round(2.675, 2))
2
2
2.67
  1. divmod(a,b)ab都是正整数时,返回的就是商和余数;当其参数有浮点数和负数时,情况较复杂,本教程不展开,详见官方文档

  2. 同为求幂计算,pow()相较于运算符**的优势在于:

  • 能进行高效的模幂运算:pow(x, y, z)的计算结果与x ** y % z相同,但用特殊的算法实现,从而在计算效率和内存使用上远优于先算幂再取模。这种运算在密码学中应用广泛。
  • 能适应函数式编程的需求:Python支持函数时编程,可以将函数作为参数传递给其他函数,此时可以传递函数pow,不能传递运算符**

6.3.3 数值计算库函数

math库有数十个函数与若干常量,涵盖数论,浮点数运算,求和求积,三角函数,指数对数,双曲函数等主题。 与复数有关的计算由cmath库处理。

表 6.6: math库的部分函数与常量
函数/常量名 说明
sqrt() 求平方根
ceil()
floor()
trunc()
向下取整
向上取整
截断小数取整
isclose() 比较浮点数是否相等
e
pi
自然常数
圆周率
表 6.7: random模块部分函数
函数名 功能
seed() 设置随机数种子
random() 返回[0,1)上的随机数
randint(a,b) 返回[a,b]上的随机整数
randrange(start, end[, step]) 返回range(start, end[, step])上的随机整数
choice(seq) 从序列seq中随机返回一个元素
choices(seq, k) 从序列seq中有放回地抽取随机k个元素
sample(seq, k) 从序列seq中无放回地抽取k个元素
shuffle(seq) 原地打乱序列seq中元素顺序

6.4 练习

  1. 用random模块,生成[1,n]之间的随机整数,n是你所在班级的人数。
  2. 计算自己的BMI指数:BMI=体重(kg)/身高^2(m^2),保留2位小数。

  1. 有效数字(significant digit)指从第一位非0数字开始,能准确表示的数字位数。↩︎