☰
Python数字类型全解析:从int、float到精度坑与随机数
2026/9/28 14:42:41 网站建设 项目流程

讲完字符串和基本语法之后,这一讲本该是最轻松的——毕竟是个人都认识数字。可真把代码跑起来,你很快就会发现这玩意儿熟悉中带着陌生:为什么0.1 + 0.2算出来是0.30000000000000004?为什么int("3.14")直接报错?为什么 Python 能轻松处理 10 后面跟一百个零,别的语言早就溢出崩了?带着这些疑问开始今天的内容:Python 基础第五讲,数字。

这一讲的价值在于把 Python 数字相关的所有核心知识点一次性串起来:三种内置数字类型、运算符、内置函数、类型转换、浮点精度、随机数,以及高频报错。从量化交易里的收益率计算,到手写数字识别(MNIST)项目里把一张 28×28 的灰度图展开成 784 个数字,再到日常爬虫里翻页参数的解析,数字无处不在。Python 被那么多领域选为主力语言,很大程度上就是因为它给数字处理提供了极大的自由度。但自由都是有代价的——底层机制不清楚,你会在精度误差、类型转换、报错处理上反复浪费时间。

这套入门系列到这里已经走完了一部分基础地形:环境搭建、变量与基本语法、字符串操作都有所涉及。现在专门用一整讲来收拾"数字"这个看似简单、实则暗坑不少的主题。适合刚学完 Python 基础语法、被浮点数精度问题弄懵的初学者,也适合半路转 Python 想补基础的朋友,以及写了一些代码但始终对int、float、bool三者关系模模糊糊的开发者。

1. 为什么单独给"数字"开一讲

1.1 Python 数字和"数学里的数字"的几个不同

数学里的数字是抽象的,Python 里的数字是对象、是类型,而且自带一套非常灵活的规则。最直观的区别有四条:

  • 不用声明类型,一个变量可以随时从整数变成浮点数,Python 是动态类型语言,变量只是名字,类型属于对象。
  • 整数没有长度上限,百万位的数字也能参与运算,范围只受内存限制,这在 C、Java 里是不可能的。
  • 布尔值本质上就是数字,True和 1 是一家人,能在算术表达式里直接当 1 用。
  • 数字运算的结果可能是"意外"的,比如10 / 3永远带着小数尾巴,不会像 C 语言那样直接给你个整数 3。

这一讲的整个思路就是围绕这四条特殊性展开。你先记住这四点,后面每一个坑都能在这里找到根源。

另外说一句背景。Python 被广泛用于数据分析、机器学习、量化交易,很大程度上就是因为它的数字处理"下限很低、上限很高"。说人话就是:新手用起来容易上手,高手用来做复杂计算也足够顺手。特别是金融领域那些收益率计算、回测统计,以及经典的手写数字识别项目,底层全是一堆数字的加减乘除、类型转换、随机抽样。数字这个基础打不牢,后面学那些高阶内容经常会莫名其妙地卡住。

1.2 提前打个预防针:这一讲你会记住的三件事

第一,int和float的边界感要拎清,什么东西天然是浮点数,什么东西天然是整数,心里有数才不会写出类型混乱的代码。第二,类型转换不是想当然就能成功的,字符串转数字没有你想象中那么无脑,里面有规则、有例外、有坑。第三,浮点数精度问题不是 Python 的 bug,而是 IEEE 754 双精度浮点数的设计使然,理解它之后你再也不会被0.30000000000000004吓到。

这三件事,说透了,这一讲就算没白学。

2. Python 内置的三种数字类型:int、float、complex

2.1 整数(int):没有上限的数字王国

Python 3 里的int是统一的大整数类型。Python 2 时代还有int和long之分,到了 Python 3 全合并成int一个了。你不需要关心一个数是不是超了位数,Python 都给你兜着。

>>> import sys >>> n = 10 ** 100 # 1 后面跟 100 个零 >>> type(n) <class 'int'> >>> n 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

你看,这么大的数在 Python 里就是一个普通int,照样加减乘除。底层靠的是任意精度整数实现,数字大了之后性能会下降,但功能层面对你完全透明。我实测过10 ** 1000000这种百万位的巨型整数做平方运算,大概零点几秒出结果,这在很多编译型语言里是不可想象的——早就溢出或者直接报错了。

这里有一个新手很容易混的点:整数除法。10 // 3结果是 3,10 % 3结果是 1,而10 / 3结果是3.3333333333333335。Python 3 里/永远是浮点除法,这是从 Python 2 升级时最大的行为变化之一。你要是从别的语言转过来,尤其是 C 系语言,一定要把"/的返回值是浮点数"这个观念刻在脑子里。

2.2 浮点数(float):精度问题的源头

Python 的float对标的是 C 语言的double,也就是 IEEE 754 双精度 64 位浮点数。这个背景知识极其重要,因为几乎所有"Python 算错"的吐槽,最终都能追溯到这一条:它只能精确表示 2 的整数次幂组合出来的数,无法精确表示所有十进制小数。

一个经典例子:

>>> 0.1 + 0.2 0.30000000000000004

看到没?不是 0.3。"够用但不精确"是对float最准确的评价。在绝大多数普通业务场景、科学计算、数据可视化里,它完全够用,但在涉及金钱、精确比较的场景里,你要有意识地绕开它。第 5 节我会专门拆解这个现象,这里你只需要建立一个警觉:浮点数可能和你"以为的结果"略有偏差。

2.3 布尔值(bool)和复数(complex)

bool是int的子类,这个设计知道的人不多,但影响深远。

>>> isinstance(True, int) True >>> True + True 2

很多新手第一次看到True + True输出 2 的时候一脸懵,这其实是 Python 类型体系里一个有意为之的设计。实际开发中,这个特性常被用来做"把布尔结果直接参与算术运算"的精巧操作,比如统计一个列表里大于 10 的元素个数:

scores = [15, 8, 12, 6, 20] count = sum(score > 10 for score in scores) # 返回 3

因为score > 10的结果是True(值为 1)或False(值为 0),sum一加就出来了。这个写法非常简洁,而且面试也爱考,建议认真理解。

复数(complex)也在 Python 内置数字类型里,基础阶段用得少,但是做信号处理、频域分析、科学计算时绕不开。

>>> z = 3 + 4j >>> z.real # 实部 3.0 >>> z.imag # 虚部 4.0 >>> abs(z) # 模长 5.0

复数支持加减乘除,abs求的是模长,本质就是勾股定理。3、4、5 这组勾股数在这里恰好对应模长 5,很多教材拿这个当例子。了解有这回事就行,基础阶段不需要深入。

3. 数字运算全家桶:运算符与内置函数

3.1 算术运算符的"隐藏四兄弟"

加减乘除不用多说。真正容易被忽略的是这四个:整除//、取余%、幂**、还有divmod()。

//是地板除,向下取整。注意是"向下"不是"截断":5 // 2的结果是 2,但-5 // 2的结果是 -3。这是很多从 C 语言转过来的程序员第一个不适应的地方:C 语言里-5 / 2结果是 -2(向零截断),Python 里-5 // 2结果是 -3(向下取整)。不是 bug,是设计,而且数学上更自洽。

%是取余,和//配套使用。-5 % 2的结果是 1,不是 -1。规律一句话总结:a % b的正负号跟随b。搞清楚这个规律,写日期偏移、循环数组下标时就不容易出 bug。

**是幂运算,2 ** 10等于 1024。还有一个pow(2, 10)也是 1024,两者在简单场景下没区别。但pow有一个隐藏大招——三参数版本:pow(x, y, mod)等价于x ** y % mod,但效率更高,因为它是用快速幂算法在内部直接完成取模,不会先把巨大的幂算出来再取模。这个在密码学、哈希计算、算法竞赛里非常实用。

divmod(a, b)同时返回(a // b, a % b),在一次除法里拿到商和余数。经典应用是把秒数转换成时分秒:

>>> total_seconds = 3671 >>> minutes, seconds = divmod(total_seconds, 60) # 61, 11 >>> hours, minutes_remain = divmod(minutes, 60) # 1, 1 >>> hours, minutes_remain, seconds (1, 1, 11)

这个例子特别适合做倒计时、视频时长格式化,一行一步,逻辑干净,不用手动写除法再取余的组合。

3.2 比较运算与优先级:if 语句里的隐形数字逻辑

比较运算符<、>、<=、>=、==、!=大家都熟,但有两个细节很值得注意。

Python 支持链式比较。3 < 4 < 5是合法的,等价于3 < 4 and 4 < 5。这在写区间判断时非常顺手,比如18 <= age < 60判断"成年人且未退休"。很多语言不支持这么写,这是 Python 独有的语法糖,用起来很舒服。

另一个更重要的细节:浮点数之间不要直接用==比较。0.1 + 0.2 == 0.3的结果是False,这个坑在第 5 节会详细讲。想判断两个浮点数是否"足够接近",用math.isclose()函数。

关于运算符优先级,不需要死记硬背,最保险的原则是:算术运算优先于比较,比较优先于布尔逻辑。写代码时如果有一丁点不确定,就加括号。可读性永远高于所谓的简洁,等你想明白优先级再精简也不迟,工作场景里没人会因为"少写了一对括号"给你加分。

3.3 一批高频内置函数:abs、round、pow、sum、max/min

abs(x)取绝对值。对整数、浮点数都适用,对复数返回的是模长,前面见过了。

round(x, ndigits)四舍五入到指定小数位。但这里藏着一个大坑:银行家舍入规则。round(0.5)的结果是 0,不是 1;round(1.5)的结果是 2。因为 Python 的round遵循"四舍六入五成双"——当值恰好在两个整数中间时,往偶数那边靠。更离谱的是round(2.675, 2)结果是2.67而不是2.68,这又是浮点数表示精度的锅:2.675在内存里实际上比2.675略小一丁点,舍入后就变成了2.67。所以,如果你需要的是传统意义上的"四舍五入",别用round,用第 5 节的Decimal模块。

sum(iterable, start=0)求和,可以传起始值。配合生成器表达式可以写出非常优雅的统计代码,前面sum(score > 10 for score in scores)就是例子。

max()/min()取最大最小值,也支持key参数。比如max(names, key=len)可以找到长度最长的名字,这个key参数在数据分析中非常常用。

pow的三参数版本在 3.1 已经讲过,不再重复。

3.4 位运算:把数字当成 01 串来操作

位运算&(按位与)、|(按位或)、^(按位异或)、~(按位取反)、<<(左移)、>>(右移),基础阶段不需要精通,但至少要认识,因为你早晚会在权限系统、状态标记、算法优化里见到它们。

比如用位运算实现权限组合:

READ = 1 # 0b001 WRITE = 2 # 0b010 EXECUTE = 4 # 0b100 # 给用户分配读和写权限 permission = READ | WRITE # 结果是 3,二进制 0b011 # 判断是否有写权限 has_write = permission & WRITE # 结果是 2,不是 0 说明有

这种写法的特点:一个整数同时携带多个开关状态,省内存、语义清晰。很多开源项目的权限设计就是这么干的。新手看不懂没关系,先把位运算符的"长相"记住,之后用到的时候再去深入学习就轻松了。

4. 数字类型转换:字符串到底怎么变成数字

4.1 三个转换函数:int、float、complex

input()读进来的永远是字符串,这是 Python 里最根深蒂固的一个事实。所以类型转换几乎是每个 Python 开发者每天都要做的操作。三个转换函数分别是int()、float()、complex()。

>>> int("123") 123 >>> float("3.14") 3.14 >>> complex("1+2j") (1+2j)

float()可以把字符串和整数转成浮点数;int()可以把字符串、浮点数、布尔值转成整数;complex()则接受字符串或两个数值。

最常见的场景是用户输入:

age = input("请输入你的年龄:") # 输入 18 print(type(age)) # <class 'str'> age = int(age) print(age + 2) # 20

注意input拿到手的东西,哪怕你肉眼看着是数字,它的类型也是字符串。忘了转换直接做算术运算,就会遇到那种让人抓狂的TypeError。

4.2 三个最经典的转换坑

坑一:int()不能直接转换带小数点的字符串。

>>> int("3.14") Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: invalid literal for int() with base 10: '3.14'

正确方式是先float()再int():int(float("3.14"))得到 3。这里int是对浮点数做向零截断,不是四舍五入。

坑二:int()对浮点数只截断不舍入。

>>> int(3.99) 3 >>> int(-3.99) -3

int(-3.99)是 -3 而不是 -4,因为int()是往零的方向取整,和//的向下取整不是一回事。想四舍五入就用round()或者后面要讲的Decimal。

坑三:bool参与算术运算会整出"灵异事件"。

>>> True * 3 3 >>> False * 3 0

不算 bug,但调试的时候如果你发现某些计算结果诡异,记得检查一下是不是有True/False混进了算式。

4.3 进制转换:bin、oct、hex

Python 内置了三种进制转换函数:bin(x)转二进制,oct(x)转八进制,hex(x)转十六进制,返回的都是字符串。

>>> bin(10) '0b1010' >>> oct(10) '0o12' >>> hex(255) '0xff'

反向转换用int()的第二个参数指定进制:int("ff", 16)得到 255,int("1010", 2)得到 10。这个在解析协议数据、处理颜色值(比如网页里的#ff00ff)、做底层开发时经常会用到。

把"字符串转数字"这条路彻底理解透之后,你会发现很多场景都是相通了。比如手写数字识别里的图像预处理:灰度图片每个像素本质上就是一个 0 到 255 的整数数组,归一化成浮点数再送进模型训练。数字类型转换在这个流程里是最底层的运输工具,没有它整个流程跑不起来。

4.4 综合小实战:写一个不会崩的 BMI 计算器

把前面这些知识点串起来,写一个命令行版 BMI 计算器:

def bmi_calculator(): try: height = float(input("请输入身高(米):")) weight = float(input("请输入体重(公斤):")) except ValueError: print("输入不合法,请确保输入的是数字!") return bmi = weight / (height ** 2) print(f"你的 BMI 是 {bmi:.1f}") if bmi < 18.5: print("体重过轻") elif bmi < 24: print("体重正常") elif bmi < 28: print("偏胖") else: print("肥胖") bmi_calculator()

这个例子用到了float转换、幂运算、格式化输出、比较判断、异常处理。为什么用float()而不是int()?因为身高和体重本来就是小数,BMI 公式需要保留精度。为什么输出用{bmi:.1f}?因为它只影响显示,不影响原始数值,而且比round()更适合做格式化输出。这个小程序麻雀虽小,五脏俱全,建议你复制到本地跑一遍,再故意输入一个abc看看异常处理是怎么兜底的。

5. 浮点数精度:0.1 加 0.2 不等于 0.3 的真相

5.1 从二进制说起:为什么 0.1 存不进去

要理解这个"诡异现象",得从二进制说起。十进制小数转二进制,规则是"乘 2 取整":0.1 换算成二进制,会得到一个无限循环小数,就像十进制里1/3 = 0.333...永远写不完一样。但计算机存储浮点数时,小数部分只能用有限的 52 位二进制来保存(IEEE 754 双精度标准),多出来的部分只能舍掉。

于是0.1在计算机里实际上是一个"非常接近 0.1 但不是 0.1"的数,0.2同样也是。当你把它们加在一起时,误差也跟着加法累积,最终结果就成了0.30000000000000004。

这不是 Python 的 bug,任何使用 IEEE 754 双精度的语言都有同样的问题。不信你打开浏览器按 F12,在开发者控制台里输入0.1 + 0.2,输出的结果一模一样。JavaScript 算不出来,Java 也算不出来,C 语言更算不出来。这是浮点数这个二进制存储方案天生带来的限制。

提示:别再传播"Python 算数都算不对"这个梗了,真相是 IEEE 754 双精度浮点数的共同"特性"。明白原理之后,它就不再是玄学了。

5.2 Decimal 模块:财务计算的正解

银行账户里的钱要是因为浮点误差少了一分钱,那是绝对不能接受的事故。所以凡是涉及金额的计算,一律优先考虑Decimal。

from decimal import Decimal Decimal("0.1") + Decimal("0.2") # Decimal('0.3')

Decimal按十进制计算,不会出现二进制误差。但注意一个极其关键的使用细节:Decimal接收的是字符串"0.1",不是浮点数0.1。如果你写Decimal(0.1),它会把那个"不精确的 float"原样转进来,结果就变成Decimal('0.1000000000000000055511151231257827021181583404541015625'),越算越乱。这是新手用Decimal最容易踩的坑——明明用了高精度模块,算出来还是带着一长串小数尾巴,原因就在传入参数的姿势不对。

Decimal还支持设置精确度:

from decimal import Decimal, getcontext, ROUND_HALF_UP getcontext().prec = 28 # 设置全局精度为 28 位有效数字 price = Decimal("19.99") quantity = Decimal("3") total = price * quantity print(total) # 59.97 # 真正意义上的四舍五入 rounded = Decimal("2.675").quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) print(rounded) # 2.68

getcontext().prec控制全局有效数字位数,默认 28 位,一般够用。quantize配合ROUND_HALF_UP可以实现传统意义上的四舍五入,弥补了round()银行家舍入的尴尬。

5.3 什么时候用 float,什么时候用 Decimal

我的建议非常直接:

  • 普通数据的展示、统计、传感器读数、科学计算、机器学习:用float。速度更快、内存更省,绝大多数场景下微小误差根本不影响结果。
  • 凡是涉及钱、税率、账单、优惠券分摊、对账:用Decimal。别犹豫,别侥幸。
  • 不要试图用float算出一个"看起来对的"结果,再写一堆修正逻辑去补误差,那是给自己找麻烦。
  • 如果只是显示层想要好看一点,f"{value:.2f}"格式化输出就能解决问题,完全没必要为此引入额外复杂度。

还有一个实用的替代方案:float之间做比较,用math.isclose()而不是==。

import math math.isclose(0.1 + 0.2, 0.3) # True

这个函数会同时考虑相对误差和绝对误差,把"近似相等"变成一个安全、可预期的判断。

6. random 模块:让数字自己"蹦"出来

6.1 五个高频随机函数

很多看似"随机"的业务逻辑——抽奖、摇号、验证码、数据打乱——背后都是random模块的功劳。下面这五个函数覆盖了 90% 的日常需求:

import random random.random() # [0, 1) 范围内的随机浮点数 random.randint(1, 100) # [1, 100] 范围内的随机整数,端点都包含 random.randrange(1, 100, 2) # 从 1 到 99 的奇数里随机取一个 random.choice(["苹果", "橘子", "香蕉"]) # 从序列中随机选一个 random.sample(range(100), 5) # 从 0-99 中不放回地随机抽 5 个

需要盯紧的坑:randint的两个端点都包含,而randrange的右端点不包含,它的参数语义和range完全一致。random.random()是左闭右开的[0, 1),永远取不到 1,这个细节在做概率模拟时非常重要。

如果想让实验结果可以被复现,可以设置随机种子:

random.seed(42) print(random.random()) # 每次运行到这里的结果都一样

种子固定之后,随机序列就变成可复现的"假随机"序列。这个特性在调试代码、对比算法效果时非常好用,强烈建议养成习惯。

6.2 掷骰子小游戏:把随机知识点串起来

写一个简单的两人掷骰子游戏,把随机、循环、比较、格式化输出全部串起来:

import random def roll_dice(): return random.randint(1, 6) scores = [0, 0] for round_no in range(1, 11): a = roll_dice() b = roll_dice() print(f"第 {round_no} 轮:A 掷出 {a},B 掷出 {b}," f"{'A 赢' if a > b else 'B 赢' if a < b else '平局'}") if a > b: scores[0] += 1 elif b > a: scores[1] += 1 print(f"最终比分:A {scores[0]} : {scores[1]} B")

运行几次你会发现,比分不会每次都一样,这就是随机的意义。如果循环次数足够大(比如一万次),A 和 B 的胜率会逐渐趋近于相等,这就是"用程序验证概率直觉"的一个小启蒙。以后做量化交易回测、模拟用户行为,底层玩的都是这一套随机抽样的逻辑。

6.3 随机数的进阶联想:从洗牌到数据科学

random.shuffle(list)可以对列表原地洗牌,这在做训练测试数据划分时很常用。经典的手写数字识别(MNIST)训练流程,第一步往往就是把样本顺序打乱,避免模型被固定的图片顺序"带偏"。量化交易里做策略回测时,也常常用随机模拟来估算策略在随机行情下的收益分布。

当然,深度学习框架里一般用 NumPy 的随机数模块,性能和可控性更好,但底层思维是一致的:用随机数生成、打乱、采样,来模拟真实世界的不确定性。作为基础课,先把random模块用明白,后面学 NumPy、PyTorch 时就能无缝切换。

7. 常见报错与排查技巧实录

7.1 三大高频报错,几乎每个新手都碰到过

报错一:TypeError——不同类型不能直接运算。

>>> 1 + "1" TypeError: unsupported operand type(s) for +: 'int' and 'str'

数字和字符串不能相加,哪怕字符串里看着就是数字。正确方式:1 + int("1")或str(1) + "1"。这个报错在爬虫解析字段、处理 Excel 数据时特别常见,因为读上来的内容往往默认都是字符串。

报错二:ValueError——字符串里根本不是数字。

>>> int("abc") ValueError: invalid literal for int() with base 10: 'abc'

用户输入乱七八糟的内容传到int()就会触发这个。写代码要有防御思维:转换之前用str.isdigit()判断,或者直接用try/except接住异常。

s = input("请输入一个整数:") if s.strip().isdigit(): n = int(s) else: print("不是合法的整数")

报错三:ZeroDivisionError——除数为零。

>>> 10 / 0 ZeroDivisionError: division by zero

有意思的是,浮点数运算允许除以零,10.0 / 0不会报错,结果是一个inf无穷大,但整数除法会直接崩溃。业务代码里,任何除法之前都应该先判断分母是否为零,或者用异常处理兜底,这个习惯必须养成。

7.2 常见问题速查表

整理一份数字操作速查表,建议直接收藏:

需求正确写法容易踩的坑
字符串转整数int("12")int("3.14")报错,需先转 float
字符串转浮点数float("3.14")float("abc")报错
向下取整math.floor(3.7)得 3int()只截断,负数时方向不同
四舍五入Decimal+ROUND_HALF_UPround(0.5)结果是 0
取绝对值abs(-3)得 3abs(3-4j)返回模长 5.0
判断字符串是否为整数"12".isdigit()isdigit不认负数和小数
输出带千分位数字f"{1234567:,}"得 1,234,567别自己手写拼接
安全判断浮点相等math.isclose(a, b)a == b可能永远为 False
随机整数random.randint(1, 100)randrange的右端点不包含

这张表是我自己在日常开发和带新人的过程中慢慢积累出来的,每一行背后都有一次真实的崩溃教训。尤其是round(0.5)这个,几乎每个团队的群里都有人发过截图。

7.3 我对数字编程的几个实操心得

先聊类型意识。我见过太多新人,写着写着把一个int变量的值赋值成字符串,然后在计算时报错,一脸茫然。一个很有效的习惯:写代码时在心里时刻给每个变量贴一个"类型标签"——name是字符串、count是整数、price是浮点数。变量名也推荐带一点类型倾向,count、total_price这种名字比a、b可读性高得多,从源头减少类型混乱。

再提一句输入处理。所有从控制台、文件、网页读进来的内容,在 Python 里几乎都是字符串。不要等出了 bug 再修,应该在入口处就统一做类型清洗,用float()或int()转一轮,再进入业务逻辑。我自己的习惯是封装一个小工具函数,把"字符串到数字"的转换和异常处理放在一起,项目里到处复用,省心省力。

最后,强烈建议你多玩 Python 交互式命令行。遇到不确定的运算符优先级、类型转换行为,直接敲一行验证一下,效果比死记硬背文档强一百倍。眼过千遍不如手过一遍,Python 的 REPL 就是为这种"随手验证"设计的,不用白不用。

对了,关于输出格式化,再分享一个彩蛋:f"{x:>10.2f}"表示宽度 10、保留两位小数、右对齐。这种格式化在输出表格对齐、打印进度数据时非常实用。格式化参数表现在不用全背,遇到时查一次就行,但你至少要记住f"{value:.2f}"这个最常见的样子——它比round()更适合做"显示层"的处理,因为原始数值没变,只是输出变好看了。这个观念,我在实际项目里反复验证过很多次,是真管用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询