Skip to content

Python 数值工程指南

Python 3.10+Core Engineering

数值是编程语言中最基础的数据类型。Python 的设计哲学使其极其易用——你不需要了解"有符号""无符号""32位""64位"这些概念,直接用就行:

python
# 无符号64位整型的最大值
>>> 2 ** 64 - 1
18446744073709551615

# 直接乘上10000也没问题,永不溢出!
>>> 18446744073709551615 * 10000
184467440737095516150000

本指南深入解析数值类型的底层机制与工程最佳实践。


一、🔢 数值基础 (Numeric Fundamentals)

1.1 三种内置数值类型

Python 中存在三种内置数值类型:

python
# 整型 (int) - 任意精度
>>> score = 100
>>> big_number = 10 ** 100  # 没有溢出问题

# 浮点型 (float) - IEEE 754 双精度
>>> temperature = 36.5
>>> scientific = 1.23e-4  # 科学计数法

# 复数 (complex)
>>> z = 3 + 4j
>>> z.real, z.imag
(3.0, 4.0)

大多数情况下,我们只需要前两种:intfloat

1.2 类型转换

python
>>> int(36.9)      # 截断小数部分
36
>>> float(100)     # 转为浮点
100.0
>>> round(36.567, 2)  # 四舍五入到2位小数
36.57

1.3 数字分隔符提升可读性

定义数值字面量时,可以使用 _ 分隔符让长数字更易读(Python 3.6+):

python
# 以"千"为单位分隔
>>> population = 1_000_000_000
>>> population
1000000000

# 十六进制也支持
>>> color = 0xFF_FF_FF

# 二进制
>>> flags = 0b1010_0101

二、⚠️ 浮点数精度陷阱 (Floating Point Pitfall)

2.1 经典问题

python
>>> 0.1 + 0.2
0.30000000000000004  # 😱 不是 0.3!

>>> 0.1 + 0.1 + 0.1 - 0.3
5.551115123125783e-17  # 不是 0!

2.2 为什么会这样?

Python 的 float 遵循 IEEE 754 双精度标准,使用 53 位二进制来近似表示十进制小数。

十进制的 0.1 在二进制中是无限循环小数

0.1 (十进制) = 0.0001100110011001100... (二进制)

由于存储位数有限,只能存储一个近似值。根据 Python 官方文档,这是所有二进制浮点数的固有限制。

2.3 危险场景

python
# ❌ 金融计算
>>> price = 0.1
>>> quantity = 3
>>> total = price * quantity
>>> total == 0.3
False  # 可能导致财务错误!

# ❌ 比较判断
>>> 0.1 + 0.2 == 0.3
False  # 条件判断失效!

三、🛡️ 精度解决方案 (Precision Solutions)

3.1 Decimal 模块

对于金融、计费等精确计算场景,使用 decimal.Decimal

python
from decimal import Decimal

# ❌ 错误:float → Decimal(已经损失精度)
>>> Decimal(0.1)
Decimal('0.100000000000000055511151231257827021181583404541015625')

# ✅ 正确:str → Decimal(保持字面量精度)
>>> Decimal('0.1') + Decimal('0.2')
Decimal('0.3')

>>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
True  # 精确比较!

关键要点

始终使用字符串初始化 Decimal。根据 Python decimal 文档,从 float 初始化会保留二进制的精度损失。

3.2 Decimal 精度控制

python
from decimal import Decimal, getcontext, ROUND_HALF_UP

# 设置全局精度(默认 28 位)
>>> getcontext().prec = 6
>>> Decimal('1') / Decimal('7')
Decimal('0.142857')

# 使用 quantize 进行精确舍入
>>> price = Decimal('19.995')
>>> price.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
Decimal('20.00')

3.3 localcontext 临时精度

使用 localcontext() 临时修改精度,不影响全局设置:

python
from decimal import Decimal, localcontext

with localcontext() as ctx:
    ctx.prec = 50  # 仅在此块内生效
    result = Decimal('1') / Decimal('7')
    print(result)
# 退出后恢复原精度

3.4 浮点数比较

如果必须使用浮点数,比较时使用 math.isclose()

python
import math

>>> 0.1 + 0.2 == 0.3
False

>>> math.isclose(0.1 + 0.2, 0.3)
True

# 自定义容差
>>> math.isclose(0.1 + 0.2, 0.3, rel_tol=1e-9, abs_tol=0.0)
True

3.5 Fraction 精确分数

对于需要精确分数运算的场景:

python
from fractions import Fraction

>>> Fraction(1, 3) + Fraction(1, 6)
Fraction(1, 2)

>>> Fraction('0.1') + Fraction('0.2')
Fraction(3, 10)

>>> float(Fraction(3, 10))
0.3

四、🎭 布尔值是数字 (Boolean as Number)

布尔类型是 整型的子类型TrueFalse 可以当作 10 使用:

python
>>> isinstance(True, int)
True

>>> int(True), int(False)
(1, 0)

>>> True + True + False
2

# 除以 False 等于除以 0
>>> 1 / False
ZeroDivisionError: division by zero

4.1 简化统计操作

利用这个特性可以大幅简化计数逻辑:

python
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# ❌ 传统方式
count = 0
for n in numbers:
    if n % 2 == 0:
        count += 1
# count = 5

# ✅ Pythonic:布尔值自动转为 0/1
count = sum(n % 2 == 0 for n in numbers)
# count = 5

# 更多示例
>>> sum(x > 5 for x in numbers)  # 大于5的个数
5
>>> sum(x < 0 for x in numbers)  # 负数个数
0

五、♾️ 特殊数值:无穷大 (Infinity)

5.1 正负无穷大

python
>>> float('inf') > 10 ** 1000
True

>>> float('-inf') < -10 ** 1000
True

>>> float('inf') + 1 == float('inf')
True

5.2 简化边界处理

利用无穷大作为哨兵值,避免繁琐的 None 判断:

python
def sort_by_age(users: list[dict]) -> list[dict]:
    """按年龄排序,缺失年龄的排到最后"""
    return sorted(
        users, 
        key=lambda u: u.get('age') if u.get('age') is not None else float('inf')
    )

users = [
    {'name': 'Alice', 'age': 25},
    {'name': 'Bob', 'age': None},
    {'name': 'Charlie', 'age': 18},
]

>>> sort_by_age(users)
[{'name': 'Charlie', 'age': 18}, {'name': 'Alice', 'age': 25}, {'name': 'Bob', 'age': None}]

5.3 NaN (Not a Number)

python
>>> nan = float('nan')
>>> nan == nan  # NaN 不等于自身!
False

>>> import math
>>> math.isnan(nan)
True

六、🔮 消除魔术数字 (Eliminating Magic Numbers)

代码中散落的数字字面量(如 13, 100)被称为"魔术数字",是维护性噩梦。

6.1 问题代码

python
def calculate_shipping(weight):
    if weight > 50:        # 50 是什么?
        return weight * 2.5
    elif weight > 10:      # 10 又是什么?
        return weight * 1.8
    return weight * 1.2    # 为什么是 1.2?

6.2 使用常量重构

python
# 定义业务常量
HEAVY_WEIGHT_THRESHOLD = 50      # kg
MEDIUM_WEIGHT_THRESHOLD = 10     # kg
HEAVY_RATE = 2.5
MEDIUM_RATE = 1.8
LIGHT_RATE = 1.2

def calculate_shipping(weight: float) -> float:
    """根据重量计算运费"""
    if weight > HEAVY_WEIGHT_THRESHOLD:
        return weight * HEAVY_RATE
    elif weight > MEDIUM_WEIGHT_THRESHOLD:
        return weight * MEDIUM_RATE
    return weight * LIGHT_RATE

6.3 使用 Enum / IntEnum

根据 Python 官方 enum 文档,枚举是管理相关常量的推荐方式:

python
from enum import IntEnum, auto

class OrderStatus(IntEnum):
    PENDING = 1
    PROCESSING = 2
    SHIPPED = 3
    DELIVERED = 4
    CANCELLED = 5

class Priority(IntEnum):
    LOW = auto()      # 自动赋值 1
    MEDIUM = auto()   # 自动赋值 2
    HIGH = auto()     # 自动赋值 3

# IntEnum 可直接与整数比较
>>> OrderStatus.PENDING == 1
True

>>> order_status = 3
>>> OrderStatus(order_status)
<OrderStatus.SHIPPED: 3>

6.4 枚举的好处

python
# ❌ 魔术数字:打错不会报错
if order.status == 13:  # 错误的数字,静默失败
    ...

# ✅ 枚举:打错会报错
if order.status == OrderStatus.SHIPPEDD:  # AttributeError!
    ...
优势说明
更易读不需要记忆数字含义
更健壮拼写错误会立即报错
IDE 支持自动补全、跳转到定义
可迭代for status in OrderStatus:

七、🚀 性能:常量折叠 (Constant Folding)

7.1 常见误区

为了性能,我们是否应该预先计算常量?

python
# 写法 A: 预计算结果
if elapsed_seconds > 950400:
    ...

# 写法 B: 保留算式(更易读)
if elapsed_seconds > 11 * 24 * 3600:
    ...

7.2 结论:选择写法 B

使用 dis 模块反汇编可以验证:

python
import dis

def check_time(elapsed_seconds):
    if elapsed_seconds > 11 * 24 * 3600:
        return True

dis.dis(check_time)

输出:

text
  2           0 LOAD_FAST                0 (elapsed_seconds)
              2 LOAD_CONST               1 (950400)  ← 编译器已计算!
              4 COMPARE_OP               0 (>)

Python 解释器在编译时会自动进行常量折叠(Constant Folding),将 11 * 24 * 3600 计算为 950400

保留算式的好处

  • ✅ 性能完全相同
  • ✅ 代码更易读(清晰表达"11天"的业务含义)
  • ✅ 无需手动计算

7.3 更多自动优化示例

python
# 以下表达式都会在编译时求值
x = 2 ** 10           # → 1024
y = 60 * 60 * 24      # → 86400
z = "Hello" + "World" # → "HelloWorld"

八、📊 数值类型对比 (Type Comparison)

类型精度性能适用场景
int无限整数计算、计数、索引
float~15位有效数字最高科学计算(可接受误差)
Decimal可配置中低金融、计费(精确计算)
Fraction精确分数数学计算(分数运算)
complex双精度实部虚部科学/工程计算

九、✅ 最佳实践清单 (Checklist)

优先级检查项描述
P0浮点精度金融/计费是否使用 Decimal?
P0Decimal 初始化是否通过字符串初始化?
P0魔术数字是否消灭了代码中的魔术数字?
P1枚举使用状态/类型是否使用 Enum/IntEnum?
P1浮点比较是否使用 math.isclose()?
P2算式可读性是否保留有业务含义的算式?
P2数字分隔符长数字是否使用 _ 分隔?
P3布尔技巧是否利用布尔值简化计数?

十、📚 延伸阅读


← 返回 Python 深度研究