| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- // 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<BisectionMethodPage> createState() => _BisectionMethodPageState();
- }
- class _BisectionMethodPageState extends State<BisectionMethodPage> {
- // 输入框在 State 里建(外壳不持有它们,见 ToolScaffold 的注释)。
- final _function = ToolField(
- '函数 f(x)',
- hint: '例如 x^3 - x - 2,变量用 x',
- // 函数要打字母、^、括号,不能只给数字键盘。
- keyboard: TextInputType.text,
- );
- 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]<tol, Break[], fa*fc<0, bb=c; fb=fc, True, aa=c; fa=fc]; '
- 'i++]; '
- '{out, c, i, Abs[bb-aa]}]';
- final parsed = parseSymjaList((await session.compute(expr)).text);
- if (parsed.length < 4) throw Exception('二分法计算没有返回预期结果');
- final rows = parsed[0];
- final cText = '${parsed[1]}';
- final iterText = '${parsed[2]}';
- final errText = '${parsed[3]}';
- // 过程表拼成**一条** ToolText:它天然是一整块。
- final lines = <String>[];
- 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 <ToolOutput>[
- 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);
- }
|