Python数字类型全解析:从int、float到精度坑与随机数
发布时间:2026/9/28 14:42:47来源:尧图网络
讲完字符串和基本语法之后这一讲本该是最轻松的——毕竟是个人都认识数字。可真把代码跑起来你很快就会发现这玩意儿熟悉中带着陌生为什么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、complex2.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结果是 310 % 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和复数complexbool是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值为 0sum一加就出来了。这个写法非常简洁而且面试也爱考建议认真理解。复数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/minabs(x)取绝对值。对整数、浮点数都适用对复数返回的是模长前面见过了。round(x, ndigits)四舍五入到指定小数位。但这里藏着一个大坑银行家舍入规则。round(0.5)的结果是 0不是 1round(1.5)的结果是 2。因为 Python 的round遵循四舍六入五成双——当值恰好在两个整数中间时往偶数那边靠。更离谱的是round(2.675, 2)结果是2.67而不是2.68这又是浮点数表示精度的锅2.675在内存里实际上比2.675略小一丁点舍入后就变成了2.67。所以如果你需要的是传统意义上的四舍五入别用round用第 5 节的Decimal模块。sum(iterable, start0)求和可以传起始值。配合生成器表达式可以写出非常优雅的统计代码前面sum(score 10 for score in scores)就是例子。max()/min()取最大最小值也支持key参数。比如max(names, keylen)可以找到长度最长的名字这个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、complexinput()读进来的永远是字符串这是 Python 里最根深蒂固的一个事实。所以类型转换几乎是每个 Python 开发者每天都要做的操作。三个转换函数分别是int()、float()、complex()。 int(123) 123 float(3.14) 3.14 complex(12j) (12j)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) -3int(-3.99)是 -3 而不是 -4因为int()是往零的方向取整和//的向下取整不是一回事。想四舍五入就用round()或者后面要讲的Decimal。坑三bool参与算术运算会整出灵异事件。 True * 3 3 False * 3 0不算 bug但调试的时候如果你发现某些计算结果诡异记得检查一下是不是有True/False混进了算式。4.3 进制转换bin、oct、hexPython 内置了三种进制转换函数bin(x)转二进制oct(x)转八进制hex(x)转十六进制返回的都是字符串。 bin(10) 0b1010 oct(10) 0o12 hex(255) 0xff反向转换用int()的第二个参数指定进制int(ff, 16)得到 255int(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), roundingROUND_HALF_UP) print(rounded) # 2.68getcontext().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()只截断负数时方向不同四舍五入DecimalROUND_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()更适合做显示层的处理因为原始数值没变只是输出变好看了。这个观念我在实际项目里反复验证过很多次是真管用。
网站建设高端定制企业官网