cuo_wei_xiang_jian.dart 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. // tools/cuo_wei_xiang_jian.dart
  2. import 'package:flutter/material.dart';
  3. import 'tool_kit.dart';
  4. /// 错位相减法。
  5. ///
  6. /// 通项 aₙ = (a·n + b)·qⁿ⁺ᵐ 的数列,前 n 项和可以写成
  7. /// Sₙ = (A·n + B)·qⁿ − B
  8. /// 其中 A = a·q^(m+1)/(q−1)、B = (b·q^(m+1) − A)/(q−1)。
  9. ///
  10. /// B 的表达式里把 A 的定义整个代进去,省掉"先算一次、把结果拼回表达式"这种来回,
  11. /// Symja 各自求值一次就够。
  12. class CuoWeiXiangJianPage extends StatefulWidget {
  13. const CuoWeiXiangJianPage({super.key});
  14. @override
  15. State<CuoWeiXiangJianPage> createState() => _CuoWeiXiangJianPageState();
  16. }
  17. class _CuoWeiXiangJianPageState extends State<CuoWeiXiangJianPage> {
  18. final _a = ToolField('a (系数)', hint: '输入分数如 1/2,或小数');
  19. final _b = ToolField('b (常数)', hint: '输入分数如 1/2,或小数');
  20. final _q = ToolField('q (公比)', hint: '输入分数如 1/2,或小数');
  21. final _m = ToolField('m (指数偏移)', hint: '整数,如 1');
  22. @override
  23. Widget build(BuildContext context) => ToolScaffold(
  24. title: '错位相减法',
  25. fields: [_a, _b, _q, _m],
  26. compute: (session) async {
  27. final a = _need(_a);
  28. final b = _need(_b);
  29. final q = _need(_q);
  30. final m = int.tryParse(_need(_m));
  31. if (m == null) {
  32. throw Exception('「m (指数偏移)」必须是整数');
  33. }
  34. // va/vb/vq/vm 这些前缀名是为了避开 Symja 的内置名(E、D、N 等)。
  35. final bind = 'va=$a,vb=$b,vq=$q,vm=$m';
  36. final aExpr = 'With[{$bind}, va*vq^(vm+1)/(vq-1)]';
  37. // B 里把 A 的定义整个代进去,不依赖上一条结果。
  38. final bExpr =
  39. 'With[{$bind}, (vb*vq^(vm+1)-(va*vq^(vm+1)/(vq-1)))/(vq-1)]';
  40. final aRes = await session.compute(aExpr);
  41. final bRes = await session.compute(bExpr);
  42. // q 也回炉一次:用户可能填 `0.5`,Symja 会规范成 `1/2`,渲染才好看
  43. final qv = (await session.compute('With[{$bind}, vq]')).text;
  44. final av = aRes.text;
  45. final bv = bRes.text;
  46. // 结果那条:Sₙ = (A·n + B)·qⁿ − B,把算出来的 A、B、q 代进去显示。
  47. // **值全是 Symja 算的**,这里只负责把数字摆进公式、分数写成 \frac。
  48. final s = sFormula(a: av, b: bv, q: qv);
  49. final aApprox = await session.compute(
  50. approxExpr('Rationalize[$aExpr]', session.approximateDigits),
  51. );
  52. final bApprox = await session.compute(
  53. approxExpr('Rationalize[$bExpr]', session.approximateDigits),
  54. );
  55. return <ToolOutput>[
  56. ToolText('结果 Sₙ = (A·n + B)·qⁿ − B', s.plain, tex: s.tex),
  57. ToolText('A', av, tex: aRes.tex),
  58. if (aApprox.text != av) ToolText('A(近似)', aApprox.text),
  59. ToolText('B', bv, tex: bRes.tex),
  60. if (bApprox.text != bv) ToolText('B(近似)', bApprox.text),
  61. ];
  62. },
  63. );
  64. /// 取一个标量输入框:空着或填了多个值都直接报中文错。
  65. String _need(ToolField f) {
  66. if (f.count == 0) throw Exception('请填写「${f.label}」');
  67. if (f.count > 1) throw Exception('「${f.label}」只能填一个数');
  68. return f.text;
  69. }
  70. }
  71. /// 拼出 Sₙ = (A·n + B)·qⁿ − B 的两种写法(给人看的纯文本 / 给渲染的 LaTeX)。
  72. ///
  73. /// 抽成顶层函数是为了能直接测:**尾巴那项是 −B**,B 取负值时该写成 `+ |B|`,
  74. /// 括号里才是 `A·n + B`(用 B 自己的符号)。这里把两者写成一样过一次,
  75. /// 结果 B = 2 时尾巴显示成 `+ 2`,整个公式是错的。
  76. ///
  77. /// [a]、[b]、[q] 都是 Symja 算出来的数值原文(`-4`、`1/2` …),这里只排版。
  78. @visibleForTesting
  79. ({String plain, String tex}) sFormula({
  80. required String a,
  81. required String b,
  82. required String q,
  83. }) => (
  84. plain:
  85. 'S(n) = ($a·n ${_signed(b, _plain)})·$q^n '
  86. '${_signed(_neg(b), _plain)}',
  87. tex:
  88. 'S_{n} = (${_frac(a)}\\cdot n ${_signed(b, _frac)})\\cdot '
  89. '{${_frac(q)}}^{n} ${_signed(_neg(b), _frac)}',
  90. );
  91. /// `+ 4` / `- \frac{1}{2}`:把带符号的数接到前一项后面,
  92. /// 不要写成 `+ (-4)` 那种。
  93. String _signed(String v, String Function(String) fmt) {
  94. final t = v.trim();
  95. return t.startsWith('-') ? '- ${fmt(t.substring(1))}' : '+ ${fmt(t)}';
  96. }
  97. String _plain(String v) => v;
  98. /// 取相反数。值都是 Symja 给的数字文本,翻个符号就行。
  99. String _neg(String v) {
  100. final t = v.trim();
  101. return t.startsWith('-') ? t.substring(1) : '-$t';
  102. }
  103. /// 数值文本 → LaTeX:分数写成 `\frac`,其余原样。值都是 Symja 给的,这里只是排版。
  104. String _frac(String v) {
  105. final t = v.trim();
  106. final m = RegExp(r'^(-?)(\d+)/(\d+)$').firstMatch(t);
  107. if (m == null) return t;
  108. return '${m[1]}\\frac{${m[2]}}{${m[3]}}';
  109. }