bisection_method.dart 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. // tools/bisection_method.dart
  2. import 'package:flutter/material.dart';
  3. import 'tool_kit.dart';
  4. /// 二分法求方程近似解。
  5. ///
  6. /// 整个算法(循环、取中点、判号、记录过程)都在 **Symja 里一次跑完** ——
  7. /// Dart 只负责拼表达式和排版。以前是 Dart 写循环、每一步回调 Symja 算一次
  8. /// f(x):既多跑好几个来回,又把"算法"劈成两半。现在一条表达式返回
  9. /// 「过程表 + 中点 + 迭代次数 + 最终误差」,拆开显示即可。
  10. class BisectionMethodPage extends StatefulWidget {
  11. const BisectionMethodPage({super.key});
  12. @override
  13. State<BisectionMethodPage> createState() => _BisectionMethodPageState();
  14. }
  15. class _BisectionMethodPageState extends State<BisectionMethodPage> {
  16. // 输入框在 State 里建(外壳不持有它们,见 ToolScaffold 的注释)。
  17. final _function = ToolField(
  18. '函数 f(x)',
  19. hint: '例如 x^3 - x - 2,变量用 x',
  20. // 函数要打字母、^、括号,不能只给数字键盘。
  21. );
  22. final _a = ToolField('区间左端点 a');
  23. final _b = ToolField('区间右端点 b');
  24. final _tolerance = ToolField('精度 tolerance');
  25. final _maxIterations = ToolField('最大迭代次数');
  26. @override
  27. void initState() {
  28. super.initState();
  29. // ToolField 没有初值参数,默认值直接填在它暴露的 controller 上。
  30. // (「重置」由外壳清空,不是恢复默认值。)
  31. _function.controller.text = 'x^3 - x - 2';
  32. _a.controller.text = '1';
  33. _b.controller.text = '2';
  34. _tolerance.controller.text = '0.0001';
  35. _maxIterations.controller.text = '100';
  36. }
  37. @override
  38. Widget build(BuildContext context) => ToolScaffold(
  39. title: '二分法求根',
  40. // 有默认值,一打开就该看到解,不用先按一下按钮。
  41. autoRun: true,
  42. fields: [_function, _a, _b, _tolerance, _maxIterations],
  43. compute: (session) async {
  44. final f = _function.text;
  45. if (f.isEmpty) throw Exception('请输入函数表达式 f(x)');
  46. if (_a.text.isEmpty || _b.text.isEmpty) {
  47. throw Exception('请填写区间端点 a 和 b');
  48. }
  49. final maxIter = int.tryParse(_maxIterations.text);
  50. if (maxIter == null || maxIter <= 0) {
  51. throw Exception('最大迭代次数必须是正整数');
  52. }
  53. // 整数端点补一个小数点,逼 Symja 走实数运算 ——
  54. // 否则中点保持精确分数,过程表里会显示 3/2 而不是 1.5。
  55. final ra = _real(_a.text);
  56. final rb = _real(_b.text);
  57. // 守卫(不是工具的数学):拿 Symja 算出的 f(a)、f(b) 和端点值,
  58. // 在 Dart 里比大小 / 比符号。
  59. final guard = parseSymjaList(
  60. (await session.compute('N[{$f /. x -> $ra, $f /. x -> $rb, $ra, $rb}]'))
  61. .text,
  62. );
  63. if (guard.length < 4) throw Exception('无法计算区间端点处的函数值');
  64. final fa = asNumber(guard[0]);
  65. final fb = asNumber(guard[1]);
  66. final aVal = asNumber(guard[2]);
  67. final bVal = asNumber(guard[3]);
  68. if (fa == null || fb == null) {
  69. throw Exception('函数在区间端点处无定义或不是实数,请检查表达式与区间');
  70. }
  71. if (aVal == null || bVal == null || aVal >= bVal) {
  72. throw Exception('区间左端点必须小于右端点(a = ${guard[2]},b = ${guard[3]})');
  73. }
  74. if (fa * fb > 0) {
  75. throw Exception('函数在区间两端点必须异号:f(a) = $fa,f(b) = $fb');
  76. }
  77. // 整个二分过程在 Symja 里跑完(循环、判号、Break 都在里面)。
  78. final expr =
  79. 'Module[{f=$f, aa=$ra, bb=$rb, tol=${_tolerance.text}, '
  80. 'i=0, c, fa, fb, fc, out={}}, '
  81. 'fa=N[f/.x->aa]; fb=N[f/.x->bb]; '
  82. 'While[Abs[bb-aa]>tol && i<$maxIter, '
  83. 'c=(aa+bb)/2; fc=N[f/.x->c]; AppendTo[out,{i+1,aa,bb,c,fc}]; '
  84. 'Which[Abs[fc]<tol, Break[], fa*fc<0, bb=c; fb=fc, True, aa=c; fa=fc]; '
  85. 'i++]; '
  86. '{out, c, i, Abs[bb-aa]}]';
  87. final parsed = parseSymjaList((await session.compute(expr)).text);
  88. if (parsed.length < 4) throw Exception('二分法计算没有返回预期结果');
  89. final rows = parsed[0];
  90. final cText = '${parsed[1]}';
  91. final iterText = '${parsed[2]}';
  92. final errText = '${parsed[3]}';
  93. // 过程表拼成**一条** ToolText:它天然是一整块。
  94. final lines = <String>[];
  95. if (rows is List) {
  96. for (final row in rows) {
  97. if (row is! List || row.length < 5) continue;
  98. lines.add(
  99. '迭代 ${row[0]}: '
  100. 'a=${_fmt6(row[1])}, '
  101. 'b=${_fmt6(row[2])}, '
  102. 'c=${_fmt6(row[3])}, '
  103. 'f(c)=${_fmt6(row[4])}',
  104. );
  105. }
  106. }
  107. return <ToolOutput>[
  108. ToolText('近似解', _fmt6(cText)),
  109. ToolText('迭代次数', iterText),
  110. ToolText('最终误差', _fmt6(errText)),
  111. ToolText(
  112. '迭代过程',
  113. lines.isEmpty ? '(区间长度一开始就不大于精度,未迭代)' : lines.join('\n'),
  114. ),
  115. ];
  116. },
  117. );
  118. }
  119. /// 纯整数字面量补 `.`,让它成为实数:`1` → `1.`、`-2` → `-2.`;
  120. /// 小数、分数、科学计数法原样保留(交给 Symja 自己解析)。
  121. String _real(String s) {
  122. final t = s.trim();
  123. return RegExp(r'^[+-]?\d+$').hasMatch(t) ? '$t.' : t;
  124. }
  125. /// 把 Symja 的数值文本格式化成 6 位小数(格式化在 Dart 里做没关系);
  126. /// 解析不了(如符号 c)就原样返回。
  127. String _fmt6(Object? v) {
  128. final s = v is String ? v : '$v';
  129. final n = asNumber(s);
  130. return n == null ? s : n.toStringAsFixed(6);
  131. }