Python 系列(五) 数值类型与运算符详解,整除、浮点精度与进制转换实战
1 | 作者:李晓辉 |
- Python 系列(一) 从零开始认识 Python,入门其实没那么难
- Python 系列(二) 学 Python 的第一步,把开发环境搭建好
- Python 系列(三) 基础语法避坑指南,缩进、大小写、续行与注释
- Python 系列(四) Python 输入输出详解,print()、input() 与常见避坑指南
- Python 系列(五) 数值类型与运算符详解,整除、浮点精度与进制转换实战
- Python 系列(六) 字符串处理实战,查找、替换、切片与日志分析
- Python 系列(七) 类型转换详解,int()、float()、str() 与金额计算避坑
- Python 系列(八) Python 流程控制入门,掌握 if、elif 和 else
- Python 系列(九) Python 逻辑运算符详解,and、or、not 一篇搞懂
- Python 系列(十) while、for 循环实战,彻底搞懂 range、break 与 continue
为什么
7 / 2的结果是3.5,而7 // 2的结果却是3?为什么
0.1 + 0.2得不到我们预期的0.3?为什么计算磁盘容量、分页数量时,一个运算符用错,结果就可能完全不一样?
这些问题看起来简单,却是编写 Python 程序时经常遇到的细节。尤其是在 Linux 运维脚本中,容量换算、资源统计、日志分析都离不开数值计算。
这一篇,我们就从数值类型开始,逐步搞懂 Python 的运算符、进制表示、类型转换和浮点精度,再通过几个实战例子,把这些知识真正用起来。
上一篇我们学习了 Python 的输入与输出,掌握了 input()、print() 以及 sep、end 等参数。
现在,程序已经能够接收用户输入,也能输出处理结果。接下来,我们要解决一个更重要的问题:如何让程序正确地计算数据?
Python 支持哪些数值类型?
Python 内置了三种主要的数值类型:
| 类型 | 示例 | 说明 |
|---|---|---|
int | 10、-20 | 整数,支持任意精度 |
float | 3.14、-0.5 | 浮点数,通常采用二进制浮点表示 |
complex | 3 + 2j | 复数,由实部和虚部组成 |
日常编写 Linux 运维脚本时,最常用的是 int 和 float。complex 在一般的服务器管理、日志分析和资源统计中用得相对较少。
先看一个简单的例子:
1 | age = 28 |
运行结果:
1 | <class 'int'> |
这里使用了 type() 函数,用来查看对象的类型。
整数 int
Python 的整数没有固定的 32 位或 64 位精度上限,可以表示非常大的整数,实际限制主要来自可用内存等系统资源。
例如:
1 | number = 123456789012345678901234567890 |
运行结果:
1 | 123456789012345678901234567890 |
对于整数而言,Python 不会仅仅因为数值超过 32 位或 64 位整数的范围,就像某些固定宽度整数类型那样发生自动溢出。
这对于处理计数、容量换算和大整数计算很方便,但也不代表计算大整数没有性能和资源成本。
浮点数 float
浮点数主要用于表示带小数的数值。
1 | cpu_usage = 78.5 |
运行结果:
1 | 78.5 |
需要注意,Python 的普通 float 通常采用 IEEE 754 双精度二进制浮点格式。这意味着很多十进制小数无法被精确表示,只能存储最接近的二进制浮点值。
后面讲解浮点精度时,我们会专门验证这个问题。
复数 complex
复数由实部和虚部组成,Python 使用 j 表示虚数单位。
1 | z = 3 + 2j |
运行结果:
1 | (3+2j) |
其中:
z.real获取实部。z.imag获取虚部。
如果没有特殊的数学计算需求,日常运维脚本通常不需要主动使用复数类型。
常用数值运算符详解
数值类型解决了数据如何表示的问题,接下来就是如何对它们进行计算。
Python 支持常见的算术运算符:
| 运算符 | 名称 | 示例 | 结果 |
|---|---|---|---|
+ | 加法 | 20 + 6 | 26 |
- | 减法 | 20 - 6 | 14 |
* | 乘法 | 20 * 6 | 120 |
/ | 除法 | 20 / 6 | 3.333... |
// | 整除 | 20 // 6 | 3 |
% | 取余 | 20 % 6 | 2 |
** | 幂运算 | 2 ** 3 | 8 |
我们把这些运算符放在一段代码中运行:
1 | x = 20 |
运行结果:
1 | x + y = 26 |
接下来重点讲解几个最容易出错的运算符。
/ 和 // 到底有什么区别?
这是 Python 初学者最容易混淆的地方。
/ 表示普通除法。两个整数使用 / 运算时,结果是浮点数,即使能够整除也是如此。
1 | print(10 / 2) |
运行结果:
1 | 5.0 |
而 // 表示向下取整的整除运算。
1 | print(10 // 2) |
运行结果:
1 | 5 |
这里需要特别注意:// 并不是简单地删除小数部分,而是向负无穷方向取整。
看下面这个例子:
1 | print(7 // 2) |
运行结果:
1 | 3 |
为什么负数的结果是 -4,而不是 -3?
因为:
7 / 2 = 3.5,向下取整得到3。-7 / 2 = -3.5,向下取整得到-4。
如果你需要的是向零截断的整数转换,int() 的行为又有所不同:
1 | print(int(3.9)) |
运行结果:
1 | 3 |
所以,在计算分页数量、资源分组等场景时,应该先弄清楚自己需要的是向下取整、向零截断,还是其他舍入规则。
% 取余运算
% 用来计算余数。
1 | print(20 % 6) |
运行结果:
1 | 2 |
取余运算在日常开发中非常实用,例如:
- 判断一个数是否为偶数。
- 判断任务是否达到指定执行次数。
- 将数据分组或循环分配。
- 计算分页过程中剩余的数据量。
例如,判断一个数是不是偶数:
1 | number = 18 |
运行结果:
1 | 偶数 |
如果一个整数除以 2 的余数为 0,就说明它是偶数。
不过,取余运算 % 也有一个容易让初学者困惑的地方。
1 | print(-7 % 2) |
运行结果:
1 | 1 |
为什么结果是 1,而不是 -1?
这是因为 Python 的整除运算 // 会向负无穷方向取整。对于 -7 // 2,结果是 -4。
Python 的整除和取余运算满足下面的关系:
1 | a == (a // b) * b + (a % b) |
因此:
1 | -7 == (-4) * 2 + 1 |
也就是 -7 == -8 + 1,所以余数是 1。
记住:Python 的整除和取余是配套的。遇到负数时,不要只凭日常的除法直觉判断余数,可以利用这个等式进行验证。
** 幂运算
** 表示幂运算。
1 | print(2 ** 3) |
运行结果:
1 | 8 |
其中,1024 ** 3 就是 (1024^3)。
在 Linux 运维场景中,计算 KiB、MiB、GiB 等二进制容量时,经常会用到这种运算。
需要注意,幂运算可能产生非常大的数值。虽然 Python 整数支持任意精度,但计算规模过大仍然可能消耗大量时间和内存。
运算符优先级:为什么计算结果和想象的不一样?
假设我们执行下面这段代码:
1 | result = 2 + 3 * 4 |
运行结果:
1 | 14 |
有些初学者可能会认为程序会先计算 2 + 3,然后再乘以 4,得到 20。
实际上,Python 会优先计算乘法,再计算加法。
常见算术运算符的优先级,可以简单记成:
- 括号
() - 幂运算
** - 正负号
+x、-x - 乘法、除法、整除、取余
*、/、//、% - 加法、减法
+、-
例如:
1 | print(2 + 3 * 4) |
运行结果:
1 | 14 |
第一条先计算乘法,第二条先计算括号内的加法,第三条先计算幂运算。
幂运算还有一个容易忽略的细节:
1 | print(2 ** 3 ** 2) |
运行结果:
1 | 512 |
这是因为连续的幂运算按从右向左的方向结合,相当于:
1 | print(2 ** (3 ** 2)) |
实战建议:遇到复杂表达式,不要过度依赖记忆运算符优先级。适当添加括号,可以让代码更清晰,也更容易进行代码审查。
数值类型转换:字符串怎么参与计算?
上一篇我们学习了 input(),知道它始终返回字符串。
这意味着,下面的代码不会得到预期的数学结果:
1 | a = input("请输入第一个数字:") |
如果分别输入 10 和 20,运行结果是:
1 | 请输入第一个数字:10 |
为什么不是 30?
因为 a 和 b 都是字符串,字符串之间使用 + 表示拼接,而不是数值加法。
需要进行数值计算时,就要进行类型转换。
使用 int() 转换为整数
1 | a = int(input("请输入第一个整数:")) |
输入 10 和 20 后,运行结果:
1 | 请输入第一个整数:10 |
int() 可以将符合要求的字符串转换为整数,也可以将浮点数转换为整数。
但是,浮点数转换为整数时,int() 是向零截断,而不是四舍五入。
1 | print(int(3.9)) |
运行结果:
1 | 3 |
此外,int("3.9") 会报 ValueError,因为带小数点的字符串不是合法的整数字符串。
使用 float() 转换为浮点数
如果输入的数据可能包含小数,就可以使用 float()。
1 | price = float(input("请输入商品价格:")) |
输入价格 19.9,数量 3 后,运行结果为:
1 | 请输入商品价格:19.9 |
具体浮点结果可能因计算而有所不同,但这个例子引出了另一个重要问题:浮点数并不总能精确表示十进制小数。
输入转换失败怎么办?
生产脚本不能假设用户永远输入正确的数据。
例如,用户在需要整数的地方输入了 abc,程序就会因为 ValueError 而异常退出。
可以使用 try/except 进行处理:
1 | try: |
如果输入 abc,程序会输出:
1 | 输入错误,请输入有效的整数。 |
对于需要长期运行的交互式脚本,还可以在发生错误后重新提示用户输入,而不是直接结束程序。
浮点数精度:为什么 0.1 + 0.2 不等于 0.3?
这是 Python 入门过程中非常经典的问题。
先运行下面这段代码:
1 | print(0.1 + 0.2) |
运行结果:
1 | 0.30000000000000004 |
明明是 0.1 + 0.2,为什么结果不是我们期待的 0.3?
原因并不是 Python 的加法出错了,而是计算机使用二进制浮点格式表示数值时,很多十进制小数无法被精确表示。
这与我们日常使用十进制表示数字类似:某些数在一种进制中能够有限表示,换成另一种进制却可能需要无限多位。
因此,普通浮点数计算的结果可能包含微小的表示误差。
比较浮点数时怎么办?
如果只是展示计算结果,可以使用 round() 控制显示精度:
1 | result = 0.1 + 0.2 |
运行结果:
1 | 0.3 |
不过,round() 不是万能的精度修复工具。它返回的仍然是相应的数值类型,不能保证所有十进制小数都变成精确的二进制浮点值。
如果需要判断两个浮点数是否足够接近,可以使用 math.isclose():
1 | import math |
运行结果:
1 | True |
对于需要容忍微小误差的数值比较,这种方式通常比直接使用 == 更合适。涉及实际业务时,还应根据数据规模和精度要求选择合适的 rel_tol 或 abs_tol。
金额计算为什么要考虑 Decimal?
如果涉及金额、价格或需要精确十进制舍入的业务,可以考虑使用标准库中的 decimal.Decimal。
例如:
1 | from decimal import Decimal |
运行结果:
1 | 59.70 |
这里使用字符串构造 Decimal,可以避免先把十进制小数转换成二进制浮点数再传入所带来的误差。
不推荐这样写:
1 | from decimal import Decimal |
因为传入的 0.1 已经是一个浮点数,Decimal 会根据这个浮点数实际表示的值进行转换,而不是自动恢复成精确的十进制 0.1。
如果业务要求固定的小数位数,还需要根据明确的舍入规则使用 quantize() 等方法进行处理。
数字字面量与进制转换
日常生活中,我们习惯使用十进制数字。但在计算机领域,二进制、八进制和十六进制也很常见。
例如,查看文件权限、分析位掩码或处理十六进制标识时,都可能接触不同的进制。
Python 如何表示不同进制?
Python 支持以下数字字面量:
| 进制 | 前缀 | 示例 | 十进制结果 |
|---|---|---|---|
| 十进制 | 无 | 255 | 255 |
| 二进制 | 0b 或 0B | 0b11111111 | 255 |
| 八进制 | 0o 或 0O | 0o377 | 255 |
| 十六进制 | 0x 或 0X | 0xff | 255 |
我们可以直接运行:
1 | decimal_value = 255 |
运行结果:
1 | 255 |
虽然源代码中的写法不同,但这些值在 Python 中都是整数,打印时默认以十进制形式显示。
这里需要记住一个细节:Python 3 不允许使用类似 077 的旧式八进制整数写法。如果要表示八进制,应使用 0o77。
如何将整数转换成其他进制?
Python 提供了几个非常方便的内置函数:
bin():转换为二进制字符串。oct():转换为八进制字符串。hex():转换为十六进制字符串。
例如:
1 | number = 255 |
运行结果:
1 | 0b11111111 |
注意,这些函数返回的是字符串,而不是另一种数值类型。
如何将不同进制的字符串转换为整数?
可以使用 int() 的第二个参数指定原始进制:
1 | print(int("ff", 16)) |
运行结果:
1 | 255 |
这在读取外部配置、处理日志或解析用户输入时很有用。
1 | hex_string = "ff" |
运行结果:
1 | 256 |
这里的 "ff" 是字符串,int(hex_string, 16) 将它按照十六进制解析为整数。
常用数值函数:abs、round、divmod 与 pow
除了基本运算符,Python 还提供了一些常用的数值函数。
abs():计算绝对值
1 | print(abs(-5)) |
运行结果:
1 | 5 |
abs() 可以用于整数、浮点数等支持绝对值运算的数值类型。
round():舍入数值
1 | print(round(3.14159, 2)) |
运行结果:
1 | 3.14 |
第二个参数表示保留的小数位数。
不过,需要注意,Python 的 round() 在遇到恰好处于两个相邻舍入结果中间的情况时,采用 ties-to-even 规则,也就是选择末位为偶数的结果。
例如:
1 | print(round(2.5)) |
运行结果:
1 | 2 |
这与某些人习惯的「逢五就进一」不同。如果金额或统计报表有明确的舍入要求,应先确认业务规则,不要想当然地依赖 round()。
divmod():一次获得商和余数
divmod(a, b) 返回一个二元组,内容相当于 (a // b, a % b)。
1 | print(divmod(20, 6)) |
运行结果:
1 | (3, 2) |
这在分页计算、批量任务分组和单位换算等场景中非常方便。
pow():计算幂
pow(a, b) 通常相当于 a ** b:
1 | print(pow(2, 10)) |
运行结果:
1 | 1024 |
但 pow() 还有一个额外能力:可以提供第三个参数,进行取模幂运算。
1 | print(pow(2, 10, 1000)) |
运行结果:
1 | 24 |
这相当于计算 (2^{10} \bmod 1000),适用于某些需要控制中间计算结果规模的数学场景。
Linux 运维实战:容量换算与分页计算
前面介绍的运算符和数值函数,真正放进实际脚本后才更容易理解。
下面通过两个小例子,把数值类型、整除、取余和幂运算串起来。
计算磁盘容量
假设我们需要将 GiB 转换成字节。
按照二进制容量单位:
- 1 KiB = 1024 字节
- 1 MiB = 1024 KiB
- 1 GiB = 1024 MiB
因此,1 GiB 等于 (1024^3) 字节。
编写如下脚本:
1 | gib = 10 |
运行结果:
1 | 容量: 10 GiB |
这里需要区分 GB 和 GiB:
- GB 是十进制单位,1 GB = 1,000,000,000 字节。
- GiB 是二进制单位,1 GiB = 1,073,741,824 字节。
在处理磁盘容量、内存容量时,应明确使用的单位,避免将两种口径混用。
如果数据来自 df、free 或其他系统命令,还要注意这些工具的输出单位和格式可能不同,不能在未确认单位的情况下直接换算。
计算分页总数
假设有 103 条记录,每页显示 10 条,我们需要计算一共需要多少页。
一个直观的方法是使用 math.ceil():
1 | import math |
运行结果:
1 | 总记录数: 103 |
也可以通过整数运算完成:
1 | total_records = 103 |
运行结果:
1 | 11 |
为什么要先加上 page_size - 1?
因为普通的 // 会向下取整。对于 103 条记录和每页 10 条的情况,直接计算 103 // 10 只能得到 10,但实际上还剩 3 条记录,需要额外一页。
通过 (total_records + page_size - 1) // page_size,就能在整数运算中实现向上取整。
在真实业务中,还需要检查 page_size 是否大于零,避免除零错误或无效的分页配置。
生产环境常见错误与排查建议
数值计算看起来简单,但在实际脚本中,下面这些问题都值得注意。
除数为零
1 | print(10 / 0) |
会引发 ZeroDivisionError。
1 | Traceback (most recent call last): |
在接收用户输入或处理外部数据时,应先检查除数是否为零。
把字符串当成数字计算
1 | number = input("请输入数字:") |
这会引发 TypeError,因为 input() 返回字符串,而这里的 1 是整数。
1 | Traceback (most recent call last): |
解决方法是先进行合适的类型转换,并对无效输入进行异常处理。
混淆整除与截断
// 是向负无穷方向取整,int() 对浮点数则是向零截断。
对于负数,这两种操作可能得到不同的结果。进行分页、时间计算或负数运算时尤其需要注意。
直接比较浮点数
对于可能存在表示误差的浮点计算,不应随意假设结果一定精确。
需要判断数值是否接近时,可以根据场景考虑 math.isclose();对于金额计算,则可以考虑 Decimal。
忽略输入范围和业务单位
将字符串转换成整数,并不意味着输入一定合理。
例如,用户输入负数的磁盘容量、零作为分页大小,或者将 GB 直接当成 GiB,都可能导致错误的业务结果。
可靠的运维脚本不仅要保证语法正确,还要检查输入类型、取值范围、单位和异常情况。
综合练习:编写一个简单的容量换算程序
现在我们把前面学习的输入输出、类型转换、算术运算和浮点数知识结合起来。
要求:
- 提示用户输入磁盘容量,单位为 GiB。
- 将输入转换为浮点数。
- 计算对应的字节数。
- 输出换算结果。
- 拒绝负数和无法转换的输入。
参考代码:
1 | try: |
例如输入 10,运行结果为:
1 | 请输入磁盘容量(GiB):10 |
因为输入使用 float() 转换,所以即使输入的是整数 10,变量 gib 仍然是浮点数,计算结果也会以浮点数形式显示。
如果这个程序要用于实际的容量管理,还可以进一步增加输入范围检查、输出格式化和单位选择功能。
写在最后
这一篇,我们从 Python 的三种数值类型出发,学习了常见算术运算符、运算符优先级、数值类型转换、浮点数精度、数字进制和常用数值函数。
更重要的是,我们通过容量换算和分页计算,理解了这些知识在真实程序中的用途。
回顾一下几个特别容易出错的地方:
/表示普通除法,两个整数相除也会得到浮点数。//是向下取整的整除运算,负数情况下尤其要注意。input()返回字符串,需要根据实际需求进行类型转换。- 浮点数存在表示误差,金额计算应根据业务要求选择合适的数值类型和舍入规则。
- 二进制、八进制和十六进制都有明确的前缀,运行时转换可以使用
int()等函数。 - 计算容量时要区分 GB 与 GiB,避免单位混淆。
掌握这些基础知识之后,编写简单的计算程序就不再困难。
