bisection_method.dart 6.3 KB

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