fractions模块 — 有理数

Python标准库精讲专题 · 数字与数学篇 · 掌握有理数精确计算

专题:Python标准库精讲系统学习

关键词:Python, 标准库, fractions, 分数, 有理数, Fraction, 分数计算, 约分

一、fractions模块概述

fractions 模块是 Python 标准库中用于有理数算术运算的核心模块。它提供了一种精确表示分数的方式,完全避免了浮点数(float)固有的精度损失问题。在日常生活中,我们经常使用分数概念(如 1/3 杯水、3/4 英里),但浮点数无法精确表示这些值——0.1 在二进制中实际上是无限循环小数,而 Fraction 对象可以精确地表示 1/10。

fractions 模块的核心类是 Fraction,它基于有理数的数学定义:一个有理数可以表示为两个整数的比值 p/q(其中 q ≠ 0)。这意味着任何可以用分数形式表达的数字都可以被精确存储和计算。在金融计算、科学测量、教育资源、食谱比例等需要精确分数的场景中,fractions 模块有着不可替代的价值。

核心优势:Fraction 对象始终保持精确的有理数表示,不会出现浮点数的舍入误差。例如 1/3 + 1/6 在浮点数中会得到近似值 0.5(实际约等于 0.49999999999999994),而 Fraction 能精确得到 1/2。

适用场景一览

与 decimal 模块的关系

Python 标准库还提供了 decimal 模块用于十进制精确计算。两者的侧重点不同:decimal 适合十进制货币计算,精度由上下文设置;Fraction 则专注于有理数的精确比值运算,结果永远是约分后的最简分数。在实际开发中,可以根据需求选择——需要控制舍入策略时用 decimal,需要纯粹的有理数算术时用 Fraction。

二、Fraction对象的创建

Fraction 提供了多种构造方式,可以根据输入数据的形态选择最合适的创建方法。

2.1 从整数对创建

最常见的构造方式:传入分子(numerator)和分母(denominator),Fraction 会自动约分并处理符号。分母不能为 0,否则会抛出 ZeroDivisionError。

# 基本分数创建 from fractions import Fraction f1 = Fraction(1, 3) # 1/3 f2 = Fraction(5, 10) # 自动约分为 1/2 f3 = Fraction(-4, 6) # 自动约分为 -2/3 f4 = Fraction(0, 7) # 分子为 0,结果为 0 f5 = Fraction(3, -5) # 符号归一化:-3/5 print(f1) # 输出: 1/3 print(f2) # 输出: 1/2 print(float(f2)) # 输出: 0.5

2.2 从字符串创建

字符串形式的分数:格式为 '分子/分母' 或纯整数字符串。这种方式特别适合解析用户输入或读取文本数据。

f1 = Fraction('3/7') # 字符串分数:3/7 f2 = Fraction('5') # 分子是 5,分母默认为 1 f3 = Fraction('-7/8') # 负分数:-7/8 f4 = Fraction(' 3 / 4 ') # 自动去除首尾空格,结果:3/4 # 错误写法(不会自动拆分字符串表达式) # Fraction('3/7 + 1/7') # ValueError

2.3 从浮点数创建(含有限分母逼近)

直接从 float 创建 Fraction 可能会产生非常大的分母,因为 float 本质上是一个二进制近似值。这时可以使用 limit_denominator() 方法来找到最接近的"人可读"分数。

# 从浮点数直接创建 f1 = Fraction(0.75) # 精确表示为 3/4 print(f1) # 输出: 3/4 f2 = Fraction(0.1) # 输出: 3602879701896397/36028797018963968 print(f2) # 这是 0.1 在二进制下的精确表示,并非真正的 1/10 # limit_denominator() 方法:找到分母不超过指定值的最接近分数 f3 = Fraction(0.1).limit_denominator(10) print(f3) # 输出: 1/10(分母最大为 10 时最接近 0.1 的分数) # 常用分母限制值 Fraction(0.333).limit_denominator(1000) # 333/1000 Fraction(0.333).limit_denominator(10) # 1/3

最佳实践:优先使用整数对或字符串创建 Fraction,避免从 float 创建再转 Fraction 这种"先引入误差再还原"的曲折路径。实在需要从近似值出发时,配合 limit_denominator 使用。

2.4 从 Decimal 创建

Fraction 还可以和 decimal.Decimal 对象互转,这在金融计算链路中非常实用。

from decimal import Decimal # Decimal 转 Fraction d = Decimal('0.1') f = Fraction(d) print(f) # 输出: 1/10 # 对比:float 转 Fraction print(Fraction(0.1)) # 输出: 3602879701896397/36028797018963968

三、算术运算与比较

Fraction 全面支持 Python 的数字运算符,所有运算结果都会自动约分为最简形式。这意味着你可以像操作普通数字一样对待 Fraction 对象。

3.1 基本算术运算

a = Fraction(1, 3) b = Fraction(1, 6) # 加法 print(a + b) # 输出: 1/2 print(a + 1) # 输出: 4/3(与整数混合运算) # 减法 print(a - b) # 输出: 1/6 print(1 - a) # 输出: 2/3 # 乘法 print(a * b) # 输出: 1/18 print(a * 3) # 输出: 1(3 * 1/3 = 1) # 除法 print(a / b) # 输出: 2(1/3 ÷ 1/6 = 2) print(a / 2) # 输出: 1/6 # 整数除法 print(a // b) # 输出: 2 print(a % b) # 输出: 0(1/3 ÷ 1/6 余 0) # 乘方 print(a ** 2) # 输出: 1/9 print(a ** -1) # 输出: 3(倒数) print(a ** Fraction(1, 2)) # 输出近似值(分数指数可能产生无理数)

3.2 比较运算

Fraction 支持所有标准的比较运算符,并且可以与 int、float 等类型直接比较。比较操作是精确的,基于分数的数学值而不是近似值。

a = Fraction(1, 3) b = Fraction(2, 6) print(a == b) # 输出: True(自动约分后相等) print(a == Fraction(1, 3)) # True print(a < Fraction(1, 2)) # True print(a > 0.3) # True(与 float 比较) print(a != Fraction(1, 4)) # True

3.3 数学函数支持

Fraction 可以无缝配合 math 模块使用,但需要注意 math 函数通常会返回 float 类型(因为结果可能不是有理数)。

import math f = Fraction(9, 4) print(float(f)) # 2.25 print(math.sqrt(float(f))) # 1.5 print(abs(Fraction(-3, 5))) # 3/5 # math.gcd 可用于手动约分(虽然 Fraction 已自动完成) print(math.gcd(12, 18)) # 6

四、分数化简与类型转换

Fraction 在创建时自动执行约分操作,确保分数始终处于最简形式。开发者也可以按需获取或转换成其他数值类型。

4.1 自动约分机制

Fraction 的内部实现使用最大公约数(GCD)算法,在初始化时自动约分。分子和分母始终保持互质(coprime),符号由分子承载。

f = Fraction(42, 56) print(f) # 输出: 3/4(自动约分) print(f.numerator) # 输出: 3(分子属性) print(f.denominator) # 输出: 4(分母属性) # 手动访问约分前后的值 from math import gcd num, den = 42, 56 g = gcd(num, den) print(f'{num//g}/{den//g}') # 输出: 3/4

4.2 as_integer_ratio() 方法

返回 (分子, 分母) 元组,与 float 的 as_integer_ratio 方法对应。这个方法在序列化和跨系统传输分数值时非常方便。

f = Fraction(7, 8) ratio = f.as_integer_ratio() print(ratio) # 输出: (7, 8) # 可用于序列化后重建 num, den = ratio reconstructed = Fraction(num, den) print(reconstructed) # 输出: 7/8

4.3 转换为浮点数

使用 float() 将 Fraction 转换为浮点数。要注意的是,如果分数的分母不是 2 的幂,转换后可能会丢失精度。

f1 = Fraction(1, 4) print(float(f1)) # 0.25(精确,因为 1/4 是 2 的幂) f2 = Fraction(1, 3) print(float(f2)) # 0.3333333333333333(近似值) f3 = Fraction(355, 113) print(float(f3)) # 3.1415929203539825(圆周率的分数近似)

4.4 与 Decimal 互转

在需要精确十进制结果的场景中,结合 Decimal 使用是最佳方案。

from decimal import Decimal, getcontext # Fraction → Decimal f = Fraction(1, 7) d = Decimal(f.numerator) / Decimal(f.denominator) print(d) # 输出: 0.1428571428571428571428571429(默认精度28位) # 控制精度 getcontext().prec = 50 d_high = Decimal(f.numerator) / Decimal(f.denominator) print(d_high) # 0.14285714285714285714285714285714285714285714285714

4.5 四舍五入与取整

Fraction 同样支持 round() 函数和 int() 类型转换。

f = Fraction(7, 3) # 7/3 ≈ 2.333... print(round(f)) # 输出: 2(四舍五入取整) print(round(f, 2)) # 输出: 2.33(保留两位小数,返回 float) print(int(f)) # 输出: 2(截断取整) print(f.numerator // f.denominator) # 输出: 2

五、实战应用

以下通过三个典型场景展示 Fraction 在实际开发中的应用价值。

5.1 场景一:比例缩放与配方调整

食谱需要按人数调整用量。假设一份蛋糕配方需要 2/3 杯糖和 1/4 杯牛奶,现在需要调整为 3 人份(原配方为 8 人份)。

from fractions import Fraction # 原始配方(8人份) original_sugar = Fraction(2, 3) # 2/3 杯糖 original_milk = Fraction(1, 4) # 1/4 杯牛奶 original_butter = Fraction(1, 2) # 1/2 杯黄油 # 缩放比例:3人份 / 8人份 = 3/8 scale = Fraction(3, 8) # 调整后的用量 new_sugar = original_sugar * scale new_milk = original_milk * scale new_butter = original_butter * scale print(f"糖:{new_sugar} 杯") # 1/4 杯 print(f"牛奶:{new_milk} 杯") # 3/32 杯 print(f"黄油:{new_butter} 杯") # 3/16 杯 # 汇总用量 total = new_sugar + new_milk + new_butter print(f"总干湿料:{total} 杯 ≈ {float(total):.2f} 杯")

5.2 场景二:概率计算

计算抽牌概率:从一副标准扑克牌(52 张)中随机抽取一张,计算抽到特定牌的概率,并以分数形式精确呈现。

from fractions import Fraction # 总牌数 total_cards = 52 # 抽到红桃的概率 hearts = 13 p_heart = Fraction(hearts, total_cards) print(f"抽到红桃的概率:{p_heart} = {float(p_heart):.4f}") # 抽到 A 的概率 aces = 4 p_ace = Fraction(aces, total_cards) print(f"抽到 A 的概率:{p_ace} = {float(p_ace):.4f}") # 抽到红桃 A 的概率 p_heart_ace = Fraction(1, total_cards) print(f"抽到红桃 A 的概率:{p_heart_ace} = {float(p_heart_ace):.4f}") # 抽到红桃或 A 的概率(容斥原理) p_heart_or_ace = p_heart + p_ace - p_heart_ace print(f"抽到红桃或 A 的概率:{p_heart_or_ace} = {float(p_heart_or_ace):.4f}") # 连续两次抽到 A 的概率(无放回) p_two_aces = Fraction(aces, total_cards) * Fraction(aces - 1, total_cards - 1) print(f"连续两次抽到 A 的概率:{p_two_aces} ≈ {float(p_two_aces):.6f}")

5.3 场景三:单位转换与精确计算

长度单位之间的精确转换。例如,将英尺(foot)和英寸(inch)之间的值用分数表示,避免浮点数舍入。

# 英寸与英尺转换:1 英尺 = 12 英寸 inch_per_foot = Fraction(1, 12) # 1 英寸 = 1/12 英尺 # 5 英寸 7 英寸 = ? 英尺 height_inch = Fraction(67, 1) # 67 英寸 height_foot = height_inch * inch_per_foot print(f"67 英寸 = {height_foot} 英尺 = {float(height_foot):.4f} 英尺") # 输出: 67 英寸 = 67/12 英尺 = 5.5833 英尺 # 3 又 1/2 英寸 + 2 又 3/4 英寸 length1 = Fraction(7, 2) # 3.5 = 7/2 length2 = Fraction(11, 4) # 2.75 = 11/4 total_length = length1 + length2 print(f"总长度 = {total_length} 英寸 = {float(total_length)} 英寸") # 输出: 总长度 = 25/4 英寸 = 6.25 英寸

六、核心总结

关键要点

常见误区

一句话记住:需要精确的有理数算术运算时用 Fraction——它给出了数学意义上的精确答案,而不是计算机意义上的近似答案。

速查表

操作/方法 说明 示例
Fraction(a, b) 创建分数 a/b,自动约分 Fraction(2, 4) → 1/2
Fraction('a/b') 从字符串创建分数 Fraction('3/8') → 3/8
limit_denominator(n) 找到分母不超过 n 的最接近分数 F(0.333).limit(10) → 1/3
as_integer_ratio() 返回 (分子, 分母) 元组 F(1,3).ratio() → (1,3)
numerator / denominator 访问分子和分母属性 f.numerator, f.denominator
float(f) / int(f) 转换为浮点数或整数 float(F(7,3)) → 2.333...