💄 Improve accurate of sqrt calculation

This commit is contained in:
2025-09-13 22:56:01 +08:00
parent e6afe6eca1
commit 3795b659f6
3 changed files with 425 additions and 47 deletions

View File

@@ -1,6 +1,7 @@
import 'dart:math';
import 'package:flutter/foundation.dart'; // For kDebugMode
import 'package:math_expressions/math_expressions.dart';
import 'package:rational/rational.dart';
import 'models/calculation_step.dart';
/// 帮助解析一元一次方程 ax+b=cx+d 的辅助类
@@ -103,8 +104,8 @@ class SolverService {
final parts = _parseLinearEquation(input);
final a = parts.a, b = parts.b, c = parts.c, d = parts.d;
final newA = a - c;
final newD = d - b;
final newA = _rationalFromDouble(a) - _rationalFromDouble(c);
final newD = _rationalFromDouble(d) - _rationalFromDouble(b);
steps.add(
CalculationStep(
@@ -121,14 +122,15 @@ class SolverService {
stepNumber: 2,
title: '合并同类项',
explanation: '合并等式两边的项。',
formula: '\$\$${newA}x = $newD\$\$',
formula:
'\$\$${newA.toDouble().toStringAsFixed(4)}x = ${newD.toDouble().toStringAsFixed(4)}\$\$',
),
);
if (newA == 0) {
if (newA == Rational.zero) {
return CalculationResult(
steps: steps,
finalAnswer: newD == 0 ? '有无穷多解' : '无解',
finalAnswer: newD == Rational.zero ? '有无穷多解' : '无解',
);
}
@@ -152,9 +154,29 @@ class SolverService {
final eqParts = input.split('=');
if (eqParts.length != 2) throw Exception("方程格式错误,应包含一个 '='。");
// Keep original equation for display
final originalEquation = _formatOriginalEquation(input);
// Parse coefficients symbolically
final leftCoeffsSymbolic = _parsePolynomialSymbolic(eqParts[0]);
final rightCoeffsSymbolic = _parsePolynomialSymbolic(eqParts[1]);
final aSymbolic = _subtractCoefficients(
leftCoeffsSymbolic[2] ?? '0',
rightCoeffsSymbolic[2] ?? '0',
);
final bSymbolic = _subtractCoefficients(
leftCoeffsSymbolic[1] ?? '0',
rightCoeffsSymbolic[1] ?? '0',
);
final cSymbolic = _subtractCoefficients(
leftCoeffsSymbolic[0] ?? '0',
rightCoeffsSymbolic[0] ?? '0',
);
// Also get numeric values for calculations
final leftCoeffs = _parsePolynomial(eqParts[0]);
final rightCoeffs = _parsePolynomial(eqParts[1]);
final a = (leftCoeffs[2] ?? 0) - (rightCoeffs[2] ?? 0);
final b = (leftCoeffs[1] ?? 0) - (rightCoeffs[1] ?? 0);
final c = (leftCoeffs[0] ?? 0) - (rightCoeffs[0] ?? 0);
@@ -168,8 +190,7 @@ class SolverService {
stepNumber: 1,
title: '整理方程',
explanation: r'将方程整理成标准形式 $ax^2+bx+c=0$。',
formula:
'\$\$${a}x^2 ${b >= 0 ? '+' : ''} ${b}x ${c >= 0 ? '+' : ''} $c = 0\$\$',
formula: originalEquation,
),
);
@@ -213,36 +234,47 @@ class SolverService {
),
);
final delta = b * b - 4 * a * c;
// Calculate delta symbolically
final deltaSymbolic = _calculateDeltaSymbolic(
aSymbolic,
bSymbolic,
cSymbolic,
);
final delta =
_rationalFromDouble(b).pow(2) -
Rational.fromInt(4) * _rationalFromDouble(a) * _rationalFromDouble(c);
steps.add(
CalculationStep(
stepNumber: 3,
title: '计算判别式 (Delta)',
explanation:
'\$\$\\Delta = b^2 - 4ac = ($b)^2 - 4 \\cdot ($a) \\cdot ($c) = $delta\$\$',
formula: '\$\$\\Delta = $delta\$\$',
explanation: '\$\$\\Delta = b^2 - 4ac = $deltaSymbolic\$\$',
formula:
'\$\$\\Delta = $deltaSymbolic = ${delta.toDouble().toStringAsFixed(4)}\$\$',
),
);
if (delta > 0) {
final x1 = (-b + sqrt(delta)) / (2 * a);
final x2 = (-b - sqrt(delta)) / (2 * a);
final deltaDouble = delta.toDouble();
if (deltaDouble > 0) {
// Keep sqrt symbolic instead of evaluating to decimal
final sqrtDeltaStr = _formatSqrtExpression(delta.toDouble());
final x1Expr = _formatQuadraticRoot(-b, sqrtDeltaStr, 2 * a, true);
final x2Expr = _formatQuadraticRoot(-b, sqrtDeltaStr, 2 * a, false);
steps.add(
CalculationStep(
stepNumber: 4,
title: '应用求根公式',
explanation:
r'因为 $\Delta > 0$,方程有两个不相等的实数根。公式: $x = \frac{-b \pm \sqrt{\Delta}}{2a}$。',
formula:
'\$\$x_1 = ${x1.toStringAsFixed(4)}, \\quad x_2 = ${x2.toStringAsFixed(4)}\$\$',
formula: '\$\$x_1 = $x1Expr, \\quad x_2 = $x2Expr\$\$',
),
);
return CalculationResult(
steps: steps,
finalAnswer:
'\$\$x_1 = ${x1.toStringAsFixed(4)}, \\quad x_2 = ${x2.toStringAsFixed(4)}\$\$',
finalAnswer: '\$\$x_1 = $x1Expr, \\quad x_2 = $x2Expr\$\$',
);
} else if (delta == 0) {
} else if (deltaDouble == 0) {
final x = -b / (2 * a);
steps.add(
CalculationStep(
@@ -266,9 +298,10 @@ class SolverService {
),
);
final sqrtDelta = sqrt(-delta);
// Keep sqrt symbolic for complex roots
final sqrtNegDeltaStr = _formatSqrtExpression(-delta.toDouble());
final realPart = -b / (2 * a);
final imagPart = sqrtDelta / (2 * a);
final imagPartExpr = _formatImaginaryPart(sqrtNegDeltaStr, 2 * a);
steps.add(
CalculationStep(
@@ -282,7 +315,7 @@ class SolverService {
return CalculationResult(
steps: steps,
finalAnswer:
'\$\$x_1 = ${realPart.toStringAsFixed(4)} + ${imagPart.toStringAsFixed(4)}i, \\quad x_2 = ${realPart.toStringAsFixed(4)} - ${imagPart.toStringAsFixed(4)}i\$\$',
'\$\$x_1 = ${realPart.toStringAsFixed(4)} + $imagPartExpr, \\quad x_2 = ${realPart.toStringAsFixed(4)} - $imagPartExpr\$\$',
);
}
}
@@ -316,16 +349,23 @@ ${a2}x ${b2 >= 0 ? '+' : ''} ${b2}y = $c2 & (2)
),
);
final det = a1 * b2 - a2 * b1;
if (det == 0) {
final det =
_rationalFromDouble(a1) * _rationalFromDouble(b2) -
_rationalFromDouble(a2) * _rationalFromDouble(b1);
if (det == Rational.zero) {
final infiniteCheck =
_rationalFromDouble(a1) * _rationalFromDouble(c2) -
_rationalFromDouble(a2) * _rationalFromDouble(c1);
return CalculationResult(
steps: steps,
finalAnswer: a1 * c2 - a2 * c1 == 0 ? '有无穷多解' : '无解',
finalAnswer: infiniteCheck == Rational.zero ? '有无穷多解' : '无解',
);
}
final newA1 = a1 * b2, newC1 = c1 * b2;
final newA2 = a2 * b1, newC2 = c2 * b1;
final newA1 = _rationalFromDouble(a1) * _rationalFromDouble(b2);
final newC1 = _rationalFromDouble(c1) * _rationalFromDouble(b2);
final newA2 = _rationalFromDouble(a2) * _rationalFromDouble(b1);
final newC2 = _rationalFromDouble(c2) * _rationalFromDouble(b1);
steps.add(
CalculationStep(
@@ -336,8 +376,8 @@ ${a2}x ${b2 >= 0 ? '+' : ''} ${b2}y = $c2 & (2)
'''
\$\$
\\begin{cases}
${newA1}x ${b1 * b2 >= 0 ? '+' : ''} ${b1 * b2}y = $newC1 & (3) \\\\
${newA2}x ${b1 * b2 >= 0 ? '+' : ''} ${b1 * b2}y = $newC2 & (4)
${newA1.toDouble().toStringAsFixed(2)}x ${b1 * b2 >= 0 ? '+' : ''} ${(b1 * b2).toStringAsFixed(2)}y = ${newC1.toDouble().toStringAsFixed(2)} & (3) \\\\
${newA2.toDouble().toStringAsFixed(2)}x ${b1 * b2 >= 0 ? '+' : ''} ${(b1 * b2).toStringAsFixed(2)}y = ${newC2.toDouble().toStringAsFixed(2)} & (4)
\\end{cases}
\$\$
''',
@@ -353,7 +393,7 @@ ${newA2}x ${b1 * b2 >= 0 ? '+' : ''} ${b1 * b2}y = $newC2 & (4)
title: '相减',
explanation: '将方程(3)减去方程(4),得到一个只含 x 的方程。',
formula:
'\$\$($newA1 - $newA2)x = $newC1 - $newC2 \\Rightarrow ${xCoeff}x = $constCoeff\$\$',
'\$\$(${newA1.toDouble().toStringAsFixed(2)} - ${newA2.toDouble().toStringAsFixed(2)})x = ${newC1.toDouble().toStringAsFixed(2)} - ${newC2.toDouble().toStringAsFixed(2)} \\Rightarrow ${xCoeff.toDouble().toStringAsFixed(2)}x = ${constCoeff.toDouble().toStringAsFixed(2)}\$\$',
),
);
@@ -369,21 +409,21 @@ ${newA2}x ${b1 * b2 >= 0 ? '+' : ''} ${b1 * b2}y = $newC2 & (4)
if (b1.abs() < 1e-9) {
final yCoeff = b2;
final yConst = c2 - a2 * x;
final yConst = c2 - a2 * x.toDouble();
final y = yConst / yCoeff;
steps.add(
CalculationStep(
stepNumber: 4,
title: '回代求解 y',
explanation: '将 x = $x 代入原方程(2)中。',
explanation: '将 x = ${x.toDouble().toStringAsFixed(4)} 代入原方程(2)中。',
formula:
'''
\$\$
\\begin{aligned}
$a2($x) + ${b2}y &= $c2 \\\\
${a2 * x} + ${b2}y &= $c2 \\\\
${b2}y &= $c2 - ${a2 * x} \\\\
${b2}y &= ${c2 - a2 * x}
$a2(${x.toDouble().toStringAsFixed(4)}) + ${b2}y &= $c2 \\\\
${a2 * x.toDouble()} + ${b2}y &= $c2 \\\\
${b2}y &= $c2 - ${a2 * x.toDouble()} \\\\
${b2}y &= ${c2 - a2 * x.toDouble()}
\\end{aligned}
\$\$
''',
@@ -394,30 +434,31 @@ ${b2}y &= ${c2 - a2 * x}
stepNumber: 5,
title: '解出 y',
explanation: '求解得到 y 的值。',
formula: '\$\$y = $y\$\$',
formula: '\$\$y = ${y.toStringAsFixed(4)}\$\$',
),
);
return CalculationResult(
steps: steps,
finalAnswer: '\$\$x = $x, \\quad y = $y\$\$',
finalAnswer:
'\$\$x = ${x.toDouble().toStringAsFixed(4)}, \\quad y = ${y.toStringAsFixed(4)}\$\$',
);
} else {
final yCoeff = b1;
final yConst = c1 - a1 * x;
final yConst = c1 - a1 * x.toDouble();
final y = yConst / yCoeff;
steps.add(
CalculationStep(
stepNumber: 4,
title: '回代求解 y',
explanation: '将 x = $x 代入原方程(1)中。',
explanation: '将 x = ${x.toDouble().toStringAsFixed(4)} 代入原方程(1)中。',
formula:
'''
\$\$
\\begin{aligned}
$a1($x) + ${b1}y &= $c1 \\\\
${a1 * x} + ${b1}y &= $c1 \\\\
${b1}y &= $c1 - ${a1 * x} \\\\
${b1}y &= ${c1 - a1 * x}
$a1(${x.toDouble().toStringAsFixed(4)}) + ${b1}y &= $c1 \\\\
${a1 * x.toDouble()} + ${b1}y &= $c1 \\\\
${b1}y &= $c1 - ${a1 * x.toDouble()} \\\\
${b1}y &= ${c1 - a1 * x.toDouble()}
\\end{aligned}
\$\$
''',
@@ -428,12 +469,13 @@ ${b1}y &= ${c1 - a1 * x}
stepNumber: 5,
title: '解出 y',
explanation: '求解得到 y 的值。',
formula: '\$\$y = $y\$\$',
formula: '\$\$y = ${y.toStringAsFixed(4)}\$\$',
),
);
return CalculationResult(
steps: steps,
finalAnswer: '\$\$x = $x, \\quad y = $y\$\$',
finalAnswer:
'\$\$x = ${x.toDouble().toStringAsFixed(4)}, \\quad y = ${y.toStringAsFixed(4)}\$\$',
);
}
}
@@ -940,4 +982,331 @@ ${b1}y &= ${c1 - a1 * x}
}
int gcd(int a, int b) => b == 0 ? a : gcd(b, a % b);
/// 格式化平方根表达式,保持符号形式
String _formatSqrtExpression(double value) {
if (value == 0) return '0';
// 处理负数(用于复数根)
if (value < 0) {
return '\\sqrt{${(-value).toInt()}}';
}
// 检查是否为完全平方数
final sqrtValue = sqrt(value);
final rounded = sqrtValue.round();
if ((sqrtValue - rounded).abs() < 1e-10) {
return rounded.toString();
}
// 寻找最大的完全平方数因子
int maxSquareFactor = 1;
int intValue = value.toInt();
for (int i = 2; i * i <= intValue; i++) {
if (intValue % (i * i) == 0) {
maxSquareFactor = i * i;
}
}
final coefficient = sqrt(maxSquareFactor).round();
final remaining = intValue ~/ maxSquareFactor;
if (remaining == 1) {
return coefficient == 1
? '\\sqrt{$intValue}'
: '$coefficient\\sqrt{$remaining}';
} else if (coefficient == 1) {
return '\\sqrt{$remaining}';
} else {
return '$coefficient\\sqrt{$remaining}';
}
}
/// 格式化二次方程的根:(-b ± sqrt(delta)) / (2a)
String _formatQuadraticRoot(
double b,
String sqrtExpr,
double denominator,
bool isPlus,
) {
final sign = isPlus ? '+' : '-';
final bStr = b == 0
? ''
: b > 0
? '${b.toInt()}'
: '(${b.toInt()})';
final denomStr = denominator == 2 ? '2' : denominator.toString();
if (b == 0) {
// 简化为 ±sqrt(delta)/denominator
if (denominator == 2) {
return isPlus
? '\\frac{\\sqrt{${sqrtExpr.replaceAll('\\sqrt{', '').replaceAll('}', '')}}}{2}'
: '-\\frac{\\sqrt{${sqrtExpr.replaceAll('\\sqrt{', '').replaceAll('}', '')}}}{2}';
} else {
return isPlus
? '\\frac{\\sqrt{${sqrtExpr.replaceAll('\\sqrt{', '').replaceAll('}', '')}}}{$denomStr}'
: '-\\frac{\\sqrt{${sqrtExpr.replaceAll('\\sqrt{', '').replaceAll('}', '')}}}{$denomStr}';
}
} else {
// 完整的表达式:(-b ± sqrt(delta))/denominator
final numerator = b > 0
? '-$bStr $sign \\sqrt{${sqrtExpr.replaceAll('\\sqrt{', '').replaceAll('}', '')}}'
: '(${b.toInt()}) $sign \\sqrt{${sqrtExpr.replaceAll('\\sqrt{', '').replaceAll('}', '')}}';
if (denominator == 2) {
return '\\frac{$numerator}{2}';
} else {
return '\\frac{$numerator}{$denomStr}';
}
}
}
/// 格式化复数根的虚部sqrt(-delta)/(2a)
String _formatImaginaryPart(String sqrtExpr, double denominator) {
final denomStr = denominator == 2 ? '2' : denominator.toString();
if (denominator == 2) {
return '\\frac{\\sqrt{${sqrtExpr.replaceAll('\\sqrt{', '').replaceAll('}', '')}}}{2}i';
} else {
return '\\frac{\\sqrt{${sqrtExpr.replaceAll('\\sqrt{', '').replaceAll('}', '')}}}{$denomStr}i';
}
}
/// 格式化原始方程,保持符号形式
String _formatOriginalEquation(String input) {
// Simply return the original equation with proper LaTeX formatting
// This avoids complex parsing issues and preserves the original symbolic form
String result = input.replaceAll(' ', '');
// 确保方程格式正确
if (!result.contains('=')) {
result = '$result=0';
}
// Replace sqrt with LaTeX format
result = result.replaceAll('sqrt(', '\\sqrt{');
result = result.replaceAll(')', '}');
return '\$\$$result\$\$';
}
/// 解析多项式,保持符号形式
Map<int, String> _parsePolynomialSymbolic(String side) {
final coeffs = <int, String>{};
// Use a simpler approach: split by terms and parse each term individually
var s = side.replaceAll(' ', ''); // Remove spaces
if (!s.startsWith('+') && !s.startsWith('-')) {
s = '+$s';
}
// Split by + and - but be more careful about parentheses and functions
final terms = <String>[];
int start = 0;
int parenDepth = 0;
for (int i = 0; i < s.length; i++) {
final char = s[i];
if (char == '(')
parenDepth++;
else if (char == ')')
parenDepth--;
// Only split on + or - when not inside parentheses
if (parenDepth == 0 && (char == '+' || char == '-') && i > start) {
terms.add(s.substring(start, i));
start = i;
}
}
terms.add(s.substring(start));
for (final term in terms) {
if (term.isEmpty) continue;
// Parse each term
final termPattern = RegExp(r'^([+-]?)(.*?)x(?:\^(\d+))?$|^([+-]?)(.*?)$');
final match = termPattern.firstMatch(term);
if (match != null) {
if (match.group(5) != null) {
// Constant term
final sign = match.group(4) ?? '+';
final value = match.group(5)!;
final coeffStr = sign == '+' && value.isNotEmpty
? value
: '$sign$value';
coeffs[0] = _combineCoefficients(coeffs[0], coeffStr);
} else {
// x term
final sign = match.group(1) ?? '+';
final coeffPart = match.group(2) ?? '';
final power = match.group(3) != null ? int.parse(match.group(3)!) : 1;
String coeffStr;
if (coeffPart.isEmpty) {
coeffStr = sign == '+' ? '1' : '-1';
} else {
coeffStr = sign == '+' ? coeffPart : '$sign$coeffPart';
}
coeffs[power] = _combineCoefficients(coeffs[power], coeffStr);
}
}
}
return coeffs;
}
/// 规范化系数字符串
String _normalizeCoefficientString(String coeff) {
if (coeff.isEmpty || coeff == '+') return '1';
if (coeff == '-') return '-1';
// 处理类似 "2sqrt(3)" 的情况
coeff = coeff.replaceAll(' ', ''); // 移除空格
// 检查是否是纯数字
final numValue = double.tryParse(coeff);
if (numValue != null) {
return coeff;
}
// 检查是否包含 sqrt
if (coeff.contains('sqrt(') || coeff.contains('\\sqrt{')) {
// 如果前面没有数字系数,默认为 1
if (coeff.startsWith('sqrt(') || coeff.startsWith('\\sqrt{')) {
return coeff.startsWith('-') ? coeff : '1' + coeff;
}
// 如果前面有数字,保持原样
return coeff;
}
return coeff;
}
/// 合并系数,保持符号形式
String _combineCoefficients(String? existing, String newCoeff) {
if (existing == null || existing == '0') return newCoeff;
if (newCoeff == '0') return existing;
// 简化逻辑:如果都是数字,可以相加;否则保持原样
final existingNum = double.tryParse(existing);
final newNum = double.tryParse(newCoeff);
if (existingNum != null && newNum != null) {
final sum = existingNum + newNum;
return sum.toString();
}
// 如果包含符号表达式,直接连接
return '$existing+$newCoeff'.replaceAll('+-', '-');
}
/// 减去系数
String _subtractCoefficients(String a, String b) {
if (a == '0') return b.startsWith('-') ? b.substring(1) : '-$b';
if (b == '0') return a;
final aNum = double.tryParse(a);
final bNum = double.tryParse(b);
if (aNum != null && bNum != null) {
final result = aNum - bNum;
return result.toString();
}
// 符号表达式相减
return '$a-${b.startsWith('-') ? b.substring(1) : b}';
}
/// 计算判别式,保持符号形式
String _calculateDeltaSymbolic(String a, String b, String c) {
// Delta = b^2 - 4ac
// 计算 b^2
String bSquared;
if (b == '0') {
bSquared = '0';
} else if (b == '1') {
bSquared = '1';
} else if (b == '-1') {
bSquared = '1';
} else if (b.startsWith('-')) {
final absB = b.substring(1);
bSquared = '$absB^2';
} else {
bSquared = '$b^2';
}
// 计算 4ac
String fourAC;
if (a == '0' || c == '0') {
fourAC = '0';
} else {
// 处理符号
String aCoeff = a;
String cCoeff = c;
// 如果 a 或 c 是负数,需要处理符号
bool aNegative = a.startsWith('-');
bool cNegative = c.startsWith('-');
if (aNegative) aCoeff = a.substring(1);
if (cNegative) cCoeff = c.substring(1);
String acProduct;
if (aCoeff == '1' && cCoeff == '1') {
acProduct = '1';
} else if (aCoeff == '1') {
acProduct = cCoeff;
} else if (cCoeff == '1') {
acProduct = aCoeff;
} else {
acProduct = '$aCoeff \\cdot $cCoeff';
}
// 确定 4ac 的符号
bool productNegative = aNegative != cNegative;
String fourACValue = '4 \\cdot $acProduct';
if (productNegative) {
fourAC = '-$fourACValue';
} else {
fourAC = fourACValue;
}
}
// 计算 Delta = b^2 - 4ac
if (bSquared == '0' && fourAC == '0') {
return '0';
} else if (bSquared == '0') {
return fourAC.startsWith('-') ? fourAC.substring(1) : '-$fourAC';
} else if (fourAC == '0') {
return bSquared;
} else {
String sign = fourAC.startsWith('-') ? '+' : '-';
String absFourAC = fourAC.startsWith('-') ? fourAC.substring(1) : fourAC;
return '$bSquared $sign $absFourAC';
}
}
Rational _rationalFromDouble(double value, {int maxPrecision = 12}) {
// 限制小数精度,避免无限循环小数
final str = value.toStringAsFixed(maxPrecision);
if (!str.contains('.')) {
return Rational.parse(str);
}
final parts = str.split('.');
final integerPart = parts[0];
final fractionalPart = parts[1];
final numerator = BigInt.parse(integerPart + fractionalPart);
final denominator = BigInt.from(10).pow(fractionalPart.length);
return Rational(numerator, denominator);
}
}

View File

@@ -424,6 +424,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.1.5+1"
rational:
dependency: "direct main"
description:
name: rational
sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336
url: "https://pub.dev"
source: hosted
version: "2.2.3"
sky_engine:
dependency: transitive
description: flutter

View File

@@ -39,6 +39,7 @@ dependencies:
google_fonts: ^6.3.1
go_router: ^14.2.0
url_launcher: ^6.3.0
rational: ^2.2.3
dev_dependencies:
flutter_test: