// tools/bisection_method.dart import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../symja.dart'; class BisectionMethodPage extends StatefulWidget { @override _BisectionMethodPageState createState() => _BisectionMethodPageState(); } class _BisectionMethodPageState extends State { final TextEditingController _functionController = TextEditingController(text: 'x^3 - x - 2'); final TextEditingController _aController = TextEditingController(text: '1'); final TextEditingController _bController = TextEditingController(text: '2'); final TextEditingController _toleranceController = TextEditingController(text: '0.0001'); final TextEditingController _maxIterationsController = TextEditingController(text: '100'); List _iterations = []; String _result = ''; bool _isCalculating = false; /// 用 Symja 求 f(x) 在 [x] 处的值。 /// /// 表达式**直接交给 Symja 解析**(以前用 math_expressions:那是另一套语法、 /// 另一套优先级,同一个式子可能"在这里算得出来、在别处又不对")。 Future _evaluateFunction(String function, double x) async { final session = context.read(); final r = await session.compute('N[$function /. x -> $x]'); // Symja 的 N[...] 输出可能带精度标记(形如 3.14`2.5),去掉再解析 final cleaned = r.text.trim().replaceAll(RegExp(r'`[0-9.]*$'), ''); final v = double.tryParse(cleaned); if (v == null) throw Exception('f($x) = ${r.text},不是数值'); return v; } Future _calculate() async { if (_isCalculating) return; setState(() { _isCalculating = true; _iterations.clear(); _result = ''; }); try { double a = double.parse(_aController.text); double b = double.parse(_bController.text); double tolerance = double.parse(_toleranceController.text); int maxIterations = int.parse(_maxIterationsController.text); String function = _functionController.text; if (a >= b) { throw Exception('区间左端点必须小于右端点'); } double fa = await _evaluateFunction(function, a); double fb = await _evaluateFunction(function, b); if (fa.isNaN || fb.isNaN) { throw Exception('函数在端点处计算失败,请检查表达式'); } if (fa * fb > 0) { throw Exception('函数在区间两端点必须异号\nf(a) = $fa, f(b) = $fb'); } int iteration = 0; double c = a; while ((b - a).abs() > tolerance && iteration < maxIterations) { c = (a + b) / 2; double fc = await _evaluateFunction(function, c); if (fc.isNaN) { throw Exception('迭代过程中函数计算失败'); } if (!mounted) return; _iterations.add('迭代 ${iteration + 1}: a=${a.toStringAsFixed(6)}, b=${b.toStringAsFixed(6)}, c=${c.toStringAsFixed(6)}, f(c)=${fc.toStringAsFixed(6)}'); if (fc.abs() < tolerance) { break; } else if (fa * fc < 0) { b = c; fb = fc; } else { a = c; fa = fc; } iteration++; } if (!mounted) return; setState(() { _result = '近似解: ${c.toStringAsFixed(8)}\n' '迭代次数: $iteration\n' '最终误差: ${(b - a).abs().toStringAsFixed(8)}'; }); } on SymjaException catch (e) { if (mounted) setState(() => _result = '错误: $e'); } catch (e) { if (mounted) setState(() => _result = '错误: $e'); } finally { if (mounted) setState(() => _isCalculating = false); } } void _clear() { setState(() { _functionController.text = 'x^3 - x - 2'; _aController.text = '1'; _bController.text = '2'; _toleranceController.text = '0.0001'; _maxIterationsController.text = '100'; _iterations.clear(); _result = ''; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('二分法求根', style: TextStyle(color: Colors.black)), backgroundColor: Colors.white, elevation: 1, iconTheme: IconThemeData(color: Colors.black), ), body: SingleChildScrollView( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '二分法求方程近似解', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.deepPurple), ), SizedBox(height: 16), Text( '使用方法:\n' '1. 输入函数表达式(支持: +, -, *, /, ^, sin, cos, tan, ln, e, sqrt 等)\n' '2. 输入有根区间 [a, b]\n' '3. 设置精度和最大迭代次数\n' '4. 点击计算获取近似解\n\n' '示例表达式:\n' '• x^3 - x - 2\n' '• sin(x) + cos(x)\n' '• e^x - 2\n' '• ln(x) + sqrt(x)', style: TextStyle(fontSize: 14, color: Colors.grey[600]), ), SizedBox(height: 20), // 函数输入 _buildInputField('函数 f(x)', '例如: x^3 - x - 2', _functionController), SizedBox(height: 16), Row( children: [ Expanded(child: _buildInputField('区间左端点 a', '1', _aController)), SizedBox(width: 16), Expanded(child: _buildInputField('区间右端点 b', '2', _bController)), ], ), SizedBox(height: 16), Row( children: [ Expanded(child: _buildInputField('精度 tolerance', '0.0001', _toleranceController)), SizedBox(width: 16), Expanded(child: _buildInputField('最大迭代次数', '100', _maxIterationsController)), ], ), SizedBox(height: 24), // 按钮 Row( children: [ Expanded( child: ElevatedButton( onPressed: _isCalculating ? null : _calculate, style: ElevatedButton.styleFrom( backgroundColor: Colors.deepPurple, padding: EdgeInsets.symmetric(vertical: 16), ), child: _isCalculating ? SizedBox(height: 20, width: 20, child: CircularProgressIndicator(color: Colors.white)) : Text('计算', style: TextStyle(fontSize: 16, color: Colors.white)), ), ), SizedBox(width: 16), Expanded( child: OutlinedButton( onPressed: _clear, style: OutlinedButton.styleFrom( padding: EdgeInsets.symmetric(vertical: 16), ), child: Text('清空', style: TextStyle(fontSize: 16)), ), ), ], ), SizedBox(height: 24), // 结果展示 if (_result.isNotEmpty) ...[ Container( width: double.infinity, padding: EdgeInsets.all(16), decoration: BoxDecoration( color: _result.contains('错误') ? Colors.red[50] : Colors.green[50], borderRadius: BorderRadius.circular(8), border: Border.all(color: _result.contains('错误') ? Colors.red : Colors.green), ), child: Text( _result, style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: _result.contains('错误') ? Colors.red[800] : Colors.green[800] ), ), ), SizedBox(height: 16), ], // 迭代过程 if (_iterations.isNotEmpty) ...[ Text('迭代过程:', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), SizedBox(height: 8), Container( height: 200, decoration: BoxDecoration( border: Border.all(color: Colors.grey[300]!), borderRadius: BorderRadius.circular(8), ), child: ListView.builder( itemCount: _iterations.length, itemBuilder: (context, index) { return Container( padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( border: index > 0 ? Border(top: BorderSide(color: Colors.grey[200]!)) : null, color: index % 2 == 0 ? Colors.grey[50] : Colors.white, ), child: Text( _iterations[index], style: TextStyle(fontSize: 12, fontFamily: 'Monospace'), ), ); }, ), ), ], ], ), ), ); } Widget _buildInputField(String label, String hint, TextEditingController controller) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: TextStyle(fontWeight: FontWeight.bold)), SizedBox(height: 4), TextField( controller: controller, decoration: InputDecoration( hintText: hint, border: OutlineInputBorder(), contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), ), ], ); } }