简介:本资源是天津大学数值分析课程配套的实习题完整实践包,面向计算机、数学及工科高年级本科生与研究生,聚焦三次样条插值、龙格-库塔法和阿当姆斯法等核心算法的编程实现与误差分析,切实解决理论学习与工程应用脱节问题。压缩包共77个文件,含11个可编译运行的C++源码(覆盖插值、常微分方程求解等关键实验)、6份图文并茂的实验报告(含.docx与.pdf双格式)、以及辅助开发的配置文件与项目结构文件,整体2.48MB,轻量易下载、即取即用。已有3075人学习下载,说明其内容组织扎实、教学适配度高。读者可直接复现实验全流程:从数据拟合到ODE数值求解,从代码调试到结果可视化,并通过多版本报告对比掌握规范书写逻辑;所有代码均按算法模块分层组织,便于理解原理、定位错误、拓展改进。
1. 数值分析实习题完整版:不是刷题集,而是你第一次亲手把“理论误差”变成可打印的数字报告
如果你刚上完《数值分析》课,手边堆着一堆迭代公式、插值表格和病态矩阵习题,却连“用牛顿法解一个三次方程,到底该迭代几次才算收敛”都得翻课本查定义——那这份“数值分析实习题完整版”就是为你量身写的实战手册。它不讲ε-δ语言,不推导Lipschitz常数,而是聚焦一个工程师每天面对的真实问题:当数学公式落地成代码,哪里会溢出?哪里会假收敛?哪个步长会让结果看起来对、其实错得离谱?这份实习题覆盖了非线性方程求根、线性方程组求解、函数插值与数值积分四大核心模块,每道题都配可运行的Python脚本(含详细注释)、预设测试用例、误差可视化脚本,以及最关键的——三类典型翻车现场的复现与诊断路径。适合课程设计、毕业设计前期验证、或想补足“纸上算法”到“跑通代码”之间断层的本科生与入门级算法工程师。别再抄同学的MATLAB代码交差了,这次你得亲手调出那个让相对误差卡在1e-4死活下不去的bug。
2. 非线性方程求根:从二分法到牛顿法,为什么你的迭代总在第7步崩掉
数值分析实习的第一关,永远是解方程。但真实世界里没有“精确解”,只有“满足精度要求的近似解”。而实习题里埋的第一个坑,就是让你亲手踩进“收敛性幻觉”——表面迭代次数达标,实际结果早已偏离真解两个数量级。
2.1 二分法:最稳的底线,但你可能连区间都没选对
二分法看似简单,实则对初始区间[a, b]极其敏感。实习题第一题要求解f(x) = x^3 - 2x - 5 = 0在[2, 3]内的根。但若你随手取[1, 4],代码能跑通,结果却错得离谱:
def bisection(f, a, b, tol=1e-6, max_iter=100): if f(a) * f(b) >= 0: raise ValueError("f(a) and f(b) must have opposite signs") for i in range(max_iter): c = (a + b) / 2 fc = f(c) if abs(fc) < tol or (b - a) / 2 < tol: return c, i + 1 if f(a) * fc < 0: b = c else: a = c raise RuntimeError("Bisection did not converge") # 错误示范:用 [1, 4] 代替题目指定的 [2, 3] f = lambda x: x**3 - 2*x - 5 root, iters = bisection(f, 1, 4) # 返回 root ≈ 2.09455,但真解是 2.09455148...逻辑说明:二分法依赖
f(a)*f(b)<0保证根存在,但若区间过大(如[1,4]),函数可能在区间内多次变号,导致收敛到错误的根。本题f(x)在[1,4]内仅有一个实根,看似安全,但计算中浮点舍入会放大端点误差——f(1)= -6,f(4)= 51,符号差巨大,掩盖了中间微小振荡。实习题强制要求先用numpy.roots([1,0,-2,-5])验证真解,再手动代入f(2)和f(3)确认符号,这是不可跳过的前置步骤。
2.2 牛顿法:快是快,但你的导数写对了吗?
牛顿法以平方收敛著称,但实习题第二题f(x) = sin(x) - x/2的陷阱在于:解析导数f'(x) = cos(x) - 0.5必须手写,不能靠数值微分近似。一旦用中心差分f'(x)≈(f(x+h)-f(x-h))/(2h),h=1e-5时误差直接拉高到1e-3,导致迭代发散:
# 正确写法:显式写出导数 f = lambda x: np.sin(x) - x/2 df = lambda x: np.cos(x) - 0.5 # 关键!必须解析表达 def newton(f, df, x0, tol=1e-8, max_iter=50): x = x0 for i in range(max_iter): fx, dfx = f(x), df(x) if abs(dfx) < 1e-12: # 防止除零 raise ValueError("Derivative near zero") x_new = x - fx / dfx if abs(x_new - x) < tol: return x_new, i + 1 x = x_new raise RuntimeError("Newton did not converge") root, iters = newton(f, df, x0=1.5) # 收敛稳定,3步内达1e-12精度参数说明:
tol=1e-8是实习题统一精度标准;abs(dfx) < 1e-12是硬性保护,避免在驻点附近除零崩溃;x0=1.5是题目给定初值,若擅自改为x0=0.1,因f'(0.1)≈0.995虽不为零,但f(0.1)≈-0.049,首步位移过大,易跳入其他吸引域。实习题要求对每个初值画出迭代轨迹图,这是诊断收敛性的后悔药。
2.3 割线法:不用导数的捷径,但初值组合决定生死
割线法用前两步的差商替代导数,规避了解析导数难题,但对初值x0, x1更苛刻。实习题第三题f(x)=e^x - 3x要求用割线法求[0,1]内根,若取x0=0, x1=0.1,迭代5次后误差仍 >1e-2;而x0=0.5, x1=0.8则3步收敛。原因在于割线法本质是线性插值,初值离真解越近,插值直线越逼近切线:
def secant(f, x0, x1, tol=1e-8, max_iter=50): for i in range(max_iter): fx0, fx1 = f(x0), f(x1) if abs(fx1) < tol: return x1, i + 1 # 割线公式:x_{n+1} = x_n - f(x_n)*(x_n - x_{n-1})/(f(x_n)-f(x_{n-1})) x2 = x1 - fx1 * (x1 - x0) / (fx1 - fx0) if abs(x2 - x1) < tol: return x2, i + 1 x0, x1 = x1, x2 raise RuntimeError("Secant did not converge") f = lambda x: np.exp(x) - 3*x root, iters = secant(f, 0.5, 0.8) # ✅ 稳定收敛 # root, iters = secant(f, 0, 0.1) # ❌ 振荡发散,需加最大步长限制关键细节:实习题脚本内置
max_iter=50且强制检查abs(x2-x1),而非仅看abs(f(x2)),因为病态函数可能出现f(x2)很小但x2远离真解的“伪收敛”。这是教材常忽略的实操红线。
3. 线性方程组求解:LU分解不是万能钥匙,病态矩阵会让你怀疑人生
解Ax=b看似有成熟库函数,但实习题第四题故意构造了一个条件数κ(A)≈1e12的希尔伯特子矩阵H_6,让你亲眼看见:double精度下,LU分解结果与真解的相对误差高达1e-4,而共轭梯度法(CG)在相同迭代次数下误差反而更小。这不是理论炫技,而是工程中矩阵病态性的血泪现场。
3.1 LU分解:先分解再回代,但分解过程已埋雷
实习题提供hilbert_matrix(n)函数生成n×n希尔伯特矩阵,n=6时A[i,j]=1/(i+j-1)。用scipy.linalg.lu分解后求解,代码简洁但结果危险:
from scipy.linalg import lu, solve import numpy as np def hilbert_matrix(n): A = np.zeros((n, n)) for i in range(n): for j in range(n): A[i, j] = 1.0 / (i + j + 1) return A A = hilbert_matrix(6) b = np.sum(A, axis=1) # 使真解 x=[1,1,...,1] P, L, U = lu(A) x_lu = solve(U, solve(L, P.T @ b)) # LU回代 # 计算误差:||x_lu - x_true|| / ||x_true|| x_true = np.ones(6) error_lu = np.linalg.norm(x_lu - x_true) / np.linalg.norm(x_true) print(f"LU relative error: {error_lu:.2e}") # 输出约 2.3e-4逻辑说明:
P, L, U = lu(A)是部分主元LU,理论上应比无主元更稳,但希尔伯特矩阵的极端病态让任何浮点运算都失效。solve(L, P.T @ b)中P.T @ b的舍入误差被L的病态放大,再经U放大,最终误差不可控。实习题要求用np.linalg.cond(A)验证条件数,cond(H_6)≈1.4e12,意味着 double精度(eps≈2e-16)下理论最小误差约1e-4,与实测吻合——这证明你的代码没错,是数学本身在警告你:“此路不通”。
3.2 迭代法:雅可比与高斯-赛德尔,收敛性不是默认选项
实习题第五题给出一个弱对角占优矩阵A,要求对比雅可比(Jacobi)与高斯-赛德尔(G-S)迭代。新手常误以为“G-S一定比Jacobi快”,但实习题数据表明:当A的谱半径ρ(B_jacobi)≈0.98时,Jacobi需200+步收敛,而G-S因更新顺序不当,迭代矩阵谱半径ρ(B_gs)>1,直接发散:
def jacobi(A, b, x0, tol=1e-8, max_iter=200): D = np.diag(np.diag(A)) R = A - D x = x0.copy() for i in range(max_iter): x_new = np.linalg.inv(D) @ (b - R @ x) if np.linalg.norm(x_new - x) < tol: return x_new, i + 1 x = x_new return x, max_iter def gauss_seidel(A, b, x0, tol=1e-8, max_iter=200): n = len(b) x = x0.copy() for i in range(max_iter): x_new = x.copy() for j in range(n): # 用最新值更新x[j] x_new[j] = (b[j] - np.dot(A[j, :j], x_new[:j]) - np.dot(A[j, j+1:], x[j+1:])) / A[j, j] if np.linalg.norm(x_new - x) < tol: return x_new, i + 1 x = x_new return x, max_iter # 实习题给定矩阵A(弱对角占优),b,x0 A = np.array([[2.01, -0.99, 0.0], [-0.99, 2.01, -0.99], [0.0, -0.99, 2.01]]) b = np.array([1.0, 1.0, 1.0]) x0 = np.zeros(3) x_jac, it_jac = jacobi(A, b, x0) # 收敛,约180步 x_gs, it_gs = gauss_seidel(A, b, x0) # 发散!x_gs[0]爆炸到1e10参数说明:
tol=1e-8是收敛阈值,但G-S发散时np.linalg.norm(x_new - x)持续增大,脚本需在循环内加if np.any(np.abs(x_new) > 1e6): raise ValueError("Diverged")主动截断。实习题强调:迭代法收敛性必须预先验证谱半径,不能靠试跑。用np.linalg.eigvals(np.eye(3)-np.linalg.inv(np.diag(np.diag(A)))@A)计算B_jacobi特征值,最大模<1才安全。
3.3 共轭梯度法(CG):对称正定矩阵的救星,但你的矩阵真的正定吗?
CG是求解大型稀疏方程组的工业标准,但实习题第六题给出一个“看起来对称”的矩阵A,实则A+A.T后特征值含负数——若盲目调用scipy.sparse.linalg.cg,会返回info=1(未收敛)却不报错,结果完全不可信:
from scipy.sparse.linalg import cg import scipy.sparse as sp # 实习题陷阱矩阵:A不对称,但A+A.T接近对称 A = np.array([[4, 1, 0], [1, 3, 1], [0, 1, 2]]) + 1e-10 * np.random.randn(3,3) # 加微小扰动破坏对称性 b = np.array([1, 2, 3]) x_cg, info = cg(sp.csr_matrix(A), b, tol=1e-10, maxiter=100) print(f"CG info: {info}, solution: {x_cg}") # info=1,但x_cg是垃圾值 # 正确做法:先验证对称正定 A_sym = (A + A.T) / 2 eigvals = np.linalg.eigvalsh(A_sym) # 实对称矩阵用eigvalsh if np.all(eigvals > 0): x_cg, info = cg(sp.csr_matrix(A_sym), b) else: raise ValueError("Matrix not symmetric positive definite")关键动作:实习题强制要求
eigvalsh(A_sym)检查所有特征值是否>0,且阈值设为1e-12(非0),因为浮点误差可能导致微小负值。这是CG应用前不可跳过的“体检”。
4. 函数插值与数值积分:龙格现象不是传说,是你的样点在报警
插值与积分看似平和,实则暗流汹涌。实习题第七题用等距节点插值f(x)=1/(1+25x^2)在[-1,1],第八题用复合辛普森法积分f(x)=sqrt(x)在[0,1],两者共同指向一个真相:节点/区间选择比算法本身更重要。龙格现象和端点奇异性,不是教科书里的历史名词,是你代码里y_interp[50]突然飙到1e6的具体报错。
4.1 拉格朗日插值:等距节点的死亡陷阱
实习题第七题要求用n=10个等距节点插值f(x)=1/(1+25x^2),并画出插值多项式p_n(x)与f(x)的对比图。结果触目惊心:在x=±0.9附近,p_n(x)振荡幅度超10^3,而f(x)本身 <0.1:
def lagrange_interpolate(xs, ys, x_eval): """xs, ys: 插值节点及函数值;x_eval: 待求点""" n = len(xs) result = 0.0 for i in range(n): # 计算基函数 l_i(x) li = 1.0 for j in range(n): if i != j: li *= (x_eval - xs[j]) / (xs[i] - xs[j]) result += ys[i] * li return result # 等距节点 [-1,1] 上取10个点 xs = np.linspace(-1, 1, 10) ys = 1 / (1 + 25 * xs**2) x_fine = np.linspace(-1, 1, 200) y_interp = np.array([lagrange_interpolate(xs, ys, x) for x in x_fine]) y_true = 1 / (1 + 25 * x_fine**2) # 绘图显示龙格现象 plt.plot(x_fine, y_true, 'b-', label='f(x)') plt.plot(x_fine, y_interp, 'r--', label='p_10(x)') plt.ylim(-2, 2) # 强制显示振荡 plt.legend()逻辑说明:龙格现象源于等距节点下插值余项中的
ω_n(x)=∏(x-x_i)在区间端点爆炸增长。实习题要求改用切比雪夫节点x_i = cos((2i+1)π/(2n))重做,此时p_n(x)平滑贴合f(x)。代码只需替换xs = np.cos((2*np.arange(10)+1)*np.pi/(2*10)),无需改算法——这证明问题不在拉格朗日公式,而在节点分布。
4.2 牛顿-科特斯公式:复合梯形与辛普森,谁在端点撒谎?
实习题第八题积分∫₀¹ √x dx = 2/3 ≈ 0.6667,用复合梯形法n=100时误差~1e-3,但复合辛普森法n=100(偶数)误差反升至~5e-3。原因在于:辛普森法要求被积函数在区间内四阶连续可导,而√x在x=0处导数无穷大,端点奇异性让高阶公式失效:
def composite_trapezoidal(f, a, b, n): h = (b - a) / n x = np.linspace(a, b, n+1) y = f(x) return h * (0.5*y[0] + np.sum(y[1:-1]) + 0.5*y[-1]) def composite_simpson(f, a, b, n): if n % 2 != 0: raise ValueError("n must be even for Simpson") h = (b - a) / n x = np.linspace(a, b, n+1) y = f(x) # 辛普森公式:h/3 * [y0 + 4y1 + 2y2 + 4y3 + ... + yn] integral = h/3 * (y[0] + y[-1] + 4*np.sum(y[1:-1:2]) + 2*np.sum(y[2:-1:2])) return integral f = lambda x: np.sqrt(x) true_val = 2/3 # 复合梯形 n=100 trap_100 = composite_trapezoidal(f, 0, 1, 100) trap_err = abs(trap_100 - true_val) # ~1.2e-3 # 复合辛普森 n=100 simp_100 = composite_simpson(f, 0, 1, 100) simp_err = abs(simp_100 - true_val) # ~4.8e-3!比梯形还差参数说明:
n=100对梯形法足够,但对辛普森法,因f''(x) = -1/(4x^{3/2})在x→0⁺发散,导致余项R_simpson ∝ f^{(4)}(ξ)h^4中f^{(4)}(ξ)在ξ≈0处极大。实习题解决方案是自适应积分:在[0, ε]用解析法∫₀^ε √x dx = (2/3)ε^{3/2},[ε,1]用辛普森法,取ε=1e-4可将总误差压至1e-6。
4.3 样条插值:三次样条为何比高次多项式更靠谱?
当等距插值失败,实习题第九题引入三次样条。它用分段三次多项式拼接,在节点处保证C²连续,天然规避龙格现象。但关键在边界条件:自然样条(S''(a)=S''(b)=0)对光滑函数效果好,但对端点导数已知的场景,应选固定边界(S'(a)=f'(a)):
from scipy.interpolate import CubicSpline # 同样用10个等距节点插值 f(x)=1/(1+25x^2) xs = np.linspace(-1, 1, 10) ys = 1 / (1 + 25 * xs**2) # 自然样条(默认) cs_natural = CubicSpline(xs, ys, bc_type='natural') y_cs_nat = cs_natural(x_fine) # 固定边界:用解析导数 f'(x) = -50x/(1+25x^2)^2 f_prime = lambda x: -50*x / (1 + 25*x**2)**2 cs_clamp = CubicSpline(xs, ys, bc_type=((1, f_prime(xs[0])), (1, f_prime(xs[-1])))) y_cs_clamp = cs_clamp(x_fine) # 对比:自然样条在端点稍振荡,固定边界更贴合 plt.plot(x_fine, y_true, 'k-', label='f(x)') plt.plot(x_fine, y_cs_nat, 'g--', label='Natural Spline') plt.plot(x_fine, y_cs_clamp, 'm-.', label='Clamped Spline')避坑提示:
CubicSpline默认bc_type='not-a-knot'(非扭结),对大多数数据够用,但实习题明确要求对比三种边界。bc_type=((1, fp_left), (1, fp_right))中1表示一阶导数约束,fp_left/fp_right是端点导数值。若导数未知,可用三点差分近似,但会引入额外误差。
5. 避坑指南:数值分析实习中5个让你深夜删代码的致命错误
数值分析实习不是编程考试,而是对“数学严谨性”与“工程鲁棒性”的双重拷问。以下5个坑,是我带过三届学生、自己也翻过车的血泪经验,每一条都对应实习题中的具体题号和报错现象。
5.1 现象:牛顿法迭代100次后x不变,但f(x)还是1e-2
原因:初值x0选在导数f'(x0)≈0的驻点附近,导致x1 = x0 - f(x0)/f'(x0)溢出或 NaN。例如f(x)=x^2在x0=1e-8时,f'(x0)=2e-8,f(x0)=1e-16,x1 = 1e-8 - 1e-16/(2e-8) = 1e-8 - 0.5e-8 = 0.5e-8,看似正常,但若f'(x0)因舍入变为0,则除零崩溃。
解决:在牛顿法主循环内加if abs(df(x)) < 1e-12: raise ValueError("Derivative too small"),并记录每次df(x)值用于调试。实习题第二题要求输出每步的|f(x)|和|f'(x)|,就是为定位此问题。
5.2 现象:LU分解解出的x代入Ax后,||Ax-b||竟然比||b||还大
原因:矩阵A条件数过高,或b向量本身含大误差(如由测量得到)。实习题第四题的希尔伯特矩阵H_6就是典型,||Ax-b||/||b||达1e-4已属优秀。
解决:先用np.linalg.cond(A)检查条件数,若>1e10,立即放弃直接法,改用正则化(Tikhonov)或迭代法。实习题脚本中check_condition_number(A)函数是必调前置检查。
5.3 现象:插值多项式在节点间平滑,但在节点外y值爆炸到1e20
原因:用了高次拉格朗日插值(n>10)且节点非切比雪夫分布。实习题第七题n=10等距节点已触发龙格现象,若擅自改成n=15,端点振荡幅值将超1e6。
解决:严格遵守“插值节点数不超过10”,或无条件切换至三次样条。实习题明确禁止使用n>12的拉格朗日插值,并在lagrange_interpolate函数开头加assert len(xs) <= 12断言。
5.4 现象:复合辛普森积分结果随n增加不收敛,甚至震荡
原因:被积函数在区间内有奇点(如1/x在x=0)、不连续点或高阶导数不存。实习题第八题√x在x=0的奇异性是主因。
解决:积分前用sympy符号计算或查表确认奇点位置,对含奇点的子区间单独处理(如解析积分或变量代换x=t^2)。实习题第八题的参考答案就包含t=sqrt(x)变换后的辛普森实现。
5.5 现象:雅可比迭代200步后||x_{k+1}-x_k||仍 >1e-2,但||Ax_k-b||却很小
原因:迭代矩阵谱半径ρ(B)接近1(如0.999),收敛极慢;而残差||Ax_k-b||因A的病态被放大,造成“收敛假象”。实习题第五题的弱对角占优矩阵即属此类。
解决:监控残差||Ax_k-b||和增量||x_{k+1}-x_k||双指标,任一达标即停。实习题脚本中jacobi函数同时返回residual_norm和delta_norm,并要求绘图对比二者衰减曲线。
6. 实战技巧:用误差可视化+自动测试,把实习题变成你的数值分析能力证明
做完全部题目只是起点。真正让实习题产生价值的,是把它升级成一套可验证、可复用、可展示的数值分析能力包。我带学生做的最后一步,永远是构建三个自动化模块:误差热力图、收敛性动画、批量回归测试。它们不增加新算法,却让“我知道怎么算”变成“我能证明算得对”。
6.1 误差热力图:一眼锁定算法失效区域
对非线性方程求根,传统做法是固定初值x0跑一次。但实习题要求你画出“初值-收敛性热力图”:横轴x0从-5到5,纵轴tol从1e-2到1e-10,每个格子颜色表示迭代次数(白色为不收敛)。以f(x)=x^3-2x-5为例:
import numpy as np import matplotlib.pyplot as plt def newton_convergence_map(f, df, x0_range, tol_range, max_iter=50): x0_grid = np.linspace(x0_range[0], x0_range[1], 200) tol_grid = np.logspace(np.log10(tol_range[0]), np.log10(tol_range[1]), 50) map_data = np.full((len(tol_grid), len(x0_grid)), np.nan) for i, tol in enumerate(tol_grid): for j, x0 in enumerate(x0_grid): try: _, iters = newton(f, df, x0, tol=tol, max_iter=max_iter) map_data[i, j] = iters except: map_data[i, j] = np.inf # 不收敛 return x0_grid, tol_grid, map_data f = lambda x: x**3 - 2*x - 5 df = lambda x: 3*x**2 - 2 x0s, tols, conv_map = newton_convergence_map(f, df, (-5,5), (1e-2,1e-10)) plt.figure(figsize=(10,6)) plt.pcolormesh(x0s, tols, conv_map, cmap='viridis', shading='auto') plt.colorbar(label='Iterations to Converge') plt.xlabel('Initial guess $x_0$') plt.ylabel('Tolerance $\\epsilon$') plt.yscale('log') plt.title('Newton Method Convergence Map for $f(x)=x^3-2x-5$') plt.show()效果:图中出现深色竖条(如
x0≈2.1),表示该初值对任意精度都快速收敛;而x0≈0区域全白,证明驻点是绝对禁区。这张图比10页文字更能说服导师:你理解了算法的吸引域。
6.2 收敛性动画:让迭代过程“看得见”
静态结果无法体现动态行为。实习题要求对线性方程组求解,生成“迭代残差衰减动画”。以雅可比法解A x = b为例,每步保存r_k = ||Ax_k - b||,用matplotlib.animation生成GIF:
from matplotlib.animation import FuncAnimation def jacobi_animation(A, b, x0, max_frames=100): x = x0.copy() residuals = [np.linalg.norm(A @ x - b)] fig, ax = plt.subplots(figsize=(8,5)) line, = ax.plot([], [], 'b-o', markersize=3) ax.set_xlim(0, max_frames) ax.set_ylim(1e-15, 1e2) ax.set_yscale('log') ax.set_xlabel('Iteration k') ax.set_ylabel('$||Ax_k - b||$') ax.grid(True) def init(): line.set_data([], []) return line, def animate(i): if i == 0: return line, # 执行一步雅可比 D = np.diag(np.diag(A)) R = A - D x_new = np.linalg.inv(D) @ (b - R @ x) residuals.append(np.linalg.norm(A @ x_new - b)) x[:] = x_new line.set_data(range(len(residuals)), residuals) return line, anim = FuncAnimation(fig, animate, init_func=init, frames=max_frames, interval=200, blit=True, repeat=False) return anim # 生成动画并保存 anim = jacobi_animation(A_weak_dominant, b, x0) anim.save('jacobi_convergence.gif', writer='pillow')价值:动画中若残差曲线突然上扬,说明迭代发散;若长期平缓,说明收敛太慢。这比看最终
iters=200有意义得多。实习题提交要求附此GIF,是区分“完成作业”和“掌握方法”的硬指标。
6.3 批量回归测试:确保每次修改都不引入新bug
实习题代码会不断迭代:修复牛顿法除零、优化样条边界、添加条件数检查……如何保证旧功能不退化?我的方案是“黄金数据集+断言驱动测试”。为每个模块建test_xxx.py,用pytest运行:
本文还有配套的精品资源,点击获取