// tools/bisection_method.dart import 'package:flutter/material.dart'; import 'tool_kit.dart'; /// 二分法求方程近似解。 /// /// 整个算法(循环、取中点、判号、记录过程)都在 **Symja 里一次跑完** —— /// Dart 只负责拼表达式和排版。以前是 Dart 写循环、每一步回调 Symja 算一次 /// f(x):既多跑好几个来回,又把"算法"劈成两半。现在一条表达式返回 /// 「过程表 + 中点 + 迭代次数 + 最终误差」,拆开显示即可。 class BisectionMethodPage extends StatefulWidget { const BisectionMethodPage({super.key}); @override State createState() => _BisectionMethodPageState(); } class _BisectionMethodPageState extends State { // 输入框在 State 里建(外壳不持有它们,见 ToolScaffold 的注释)。 final _function = ToolField( '函数 f(x)', hint: '例如 x^3 - x - 2,变量用 x', // 函数要打字母、^、括号,不能只给数字键盘。 ); final _a = ToolField('区间左端点 a'); final _b = ToolField('区间右端点 b'); final _tolerance = ToolField('精度 tolerance'); final _maxIterations = ToolField('最大迭代次数'); @override void initState() { super.initState(); // ToolField 没有初值参数,默认值直接填在它暴露的 controller 上。 // (「重置」由外壳清空,不是恢复默认值。) _function.controller.text = 'x^3 - x - 2'; _a.controller.text = '1'; _b.controller.text = '2'; _tolerance.controller.text = '0.0001'; _maxIterations.controller.text = '100'; } @override Widget build(BuildContext context) => ToolScaffold( title: '二分法求根', // 有默认值,一打开就该看到解,不用先按一下按钮。 autoRun: true, fields: [_function, _a, _b, _tolerance, _maxIterations], compute: (session) async { final f = _function.text; if (f.isEmpty) throw Exception('请输入函数表达式 f(x)'); if (_a.text.isEmpty || _b.text.isEmpty) { throw Exception('请填写区间端点 a 和 b'); } final maxIter = int.tryParse(_maxIterations.text); if (maxIter == null || maxIter <= 0) { throw Exception('最大迭代次数必须是正整数'); } // 整数端点补一个小数点,逼 Symja 走实数运算 —— // 否则中点保持精确分数,过程表里会显示 3/2 而不是 1.5。 final ra = _real(_a.text); final rb = _real(_b.text); // 守卫(不是工具的数学):拿 Symja 算出的 f(a)、f(b) 和端点值, // 在 Dart 里比大小 / 比符号。 final guard = parseSymjaList( (await session.compute('N[{$f /. x -> $ra, $f /. x -> $rb, $ra, $rb}]')) .text, ); if (guard.length < 4) throw Exception('无法计算区间端点处的函数值'); final fa = asNumber(guard[0]); final fb = asNumber(guard[1]); final aVal = asNumber(guard[2]); final bVal = asNumber(guard[3]); if (fa == null || fb == null) { throw Exception('函数在区间端点处无定义或不是实数,请检查表达式与区间'); } if (aVal == null || bVal == null || aVal >= bVal) { throw Exception('区间左端点必须小于右端点(a = ${guard[2]},b = ${guard[3]})'); } if (fa * fb > 0) { throw Exception('函数在区间两端点必须异号:f(a) = $fa,f(b) = $fb'); } // 整个二分过程在 Symja 里跑完(循环、判号、Break 都在里面)。 final expr = 'Module[{f=$f, aa=$ra, bb=$rb, tol=${_tolerance.text}, ' 'i=0, c, fa, fb, fc, out={}}, ' 'fa=N[f/.x->aa]; fb=N[f/.x->bb]; ' 'While[Abs[bb-aa]>tol && i<$maxIter, ' 'c=(aa+bb)/2; fc=N[f/.x->c]; AppendTo[out,{i+1,aa,bb,c,fc}]; ' 'Which[Abs[fc][]; if (rows is List) { for (final row in rows) { if (row is! List || row.length < 5) continue; lines.add( '迭代 ${row[0]}: ' 'a=${_fmt6(row[1])}, ' 'b=${_fmt6(row[2])}, ' 'c=${_fmt6(row[3])}, ' 'f(c)=${_fmt6(row[4])}', ); } } return [ ToolText('近似解', _fmt6(cText)), ToolText('迭代次数', iterText), ToolText('最终误差', _fmt6(errText)), ToolText( '迭代过程', lines.isEmpty ? '(区间长度一开始就不大于精度,未迭代)' : lines.join('\n'), ), ]; }, ); } /// 纯整数字面量补 `.`,让它成为实数:`1` → `1.`、`-2` → `-2.`; /// 小数、分数、科学计数法原样保留(交给 Symja 自己解析)。 String _real(String s) { final t = s.trim(); return RegExp(r'^[+-]?\d+$').hasMatch(t) ? '$t.' : t; } /// 把 Symja 的数值文本格式化成 6 位小数(格式化在 Dart 里做没关系); /// 解析不了(如符号 c)就原样返回。 String _fmt6(Object? v) { final s = v is String ? v : '$v'; final n = asNumber(s); return n == null ? s : n.toStringAsFixed(6); }