Support calculate ^0.5

This commit is contained in:
2025-09-14 13:42:14 +08:00
parent c9190d05a1
commit e6a52b8b74
4 changed files with 149 additions and 16 deletions

View File

@@ -1,5 +1,5 @@
// === 在 abstract class Expr 中添加声明 === // === 在 abstract class Expr 中添加声明 ===
import 'dart:math' show sqrt, cos, sin, tan; import 'dart:math' show sqrt, cos, sin, tan, pow;
abstract class Expr { abstract class Expr {
Expr simplify(); Expr simplify();
@@ -488,7 +488,7 @@ class SqrtExpr extends Expr {
SqrtExpr(inner.substitute(varName, value)); SqrtExpr(inner.substitute(varName, value));
@override @override
String toString() => "sqrt($inner)"; String toString() => "\\sqrt{${inner.toString()}}";
} }
// === CosExpr === // === CosExpr ===
@@ -584,6 +584,90 @@ class TanExpr extends Expr {
String toString() => "tan($inner)"; String toString() => "tan($inner)";
} }
// === PowExpr ===
class PowExpr extends Expr {
final Expr left, right;
PowExpr(this.left, this.right);
@override
Expr simplify() {
var l = left.simplify();
var r = right.simplify();
// x^0 = 1
if (r is IntExpr && r.value == 0) return IntExpr(1);
// x^1 = x
if (r is IntExpr && r.value == 1) return l;
// 1^x = 1
if (l is IntExpr && l.value == 1) return IntExpr(1);
// 0^x = 0 (for x != 0)
if (l is IntExpr && l.value == 0 && !(r is IntExpr && r.value == 0)) {
return IntExpr(0);
}
return PowExpr(l, r);
}
@override
Expr evaluate() {
var l = left.evaluate();
var r = right.evaluate();
// x^0 = 1
if (r is IntExpr && r.value == 0) return IntExpr(1);
// x^1 = x
if (r is IntExpr && r.value == 1) return l;
// 1^x = 1
if (l is IntExpr && l.value == 1) return IntExpr(1);
// 0^x = 0 (for x != 0)
if (l is IntExpr && l.value == 0 && !(r is IntExpr && r.value == 0))
return IntExpr(0);
// If both are numbers, compute
if (l is IntExpr && r is IntExpr) {
return DoubleExpr(pow(l.value.toDouble(), r.value.toDouble()).toDouble());
}
if (l is DoubleExpr && r is IntExpr) {
return DoubleExpr(pow(l.value, r.value.toDouble()).toDouble());
}
if (l is IntExpr && r is DoubleExpr) {
return DoubleExpr(pow(l.value.toDouble(), r.value).toDouble());
}
if (l is DoubleExpr && r is DoubleExpr) {
return DoubleExpr(pow(l.value, r.value).toDouble());
}
return PowExpr(l, r);
}
@override
Expr substitute(String varName, Expr value) => PowExpr(
left.substitute(varName, value),
right.substitute(varName, value),
);
@override
String toString() {
String leftStr = left.toString();
String rightStr = right.toString();
// Remove outer parentheses
if (leftStr.startsWith('(') && leftStr.endsWith(')')) {
leftStr = leftStr.substring(1, leftStr.length - 1);
}
if (rightStr.startsWith('(') && rightStr.endsWith(')')) {
rightStr = rightStr.substring(1, rightStr.length - 1);
}
// Add parentheses around base if it's a complex expression
bool needsParens =
!(left is VarExpr || left is IntExpr || left is DoubleExpr);
String base = needsParens ? '($leftStr)' : leftStr;
return '$base^{$rightStr}';
}
}
// === 辅助:识别 a * sqrt(X) 形式 === // === 辅助:识别 a * sqrt(X) 形式 ===
class _SqrtTerm { class _SqrtTerm {
final int coef; final int coef;

View File

@@ -33,12 +33,12 @@ class Parser {
} }
Expr parseMul() { Expr parseMul() {
var expr = parseAtom(); var expr = parsePow();
skipSpaces(); skipSpaces();
while (!isEnd && (current == '*' || current == '/')) { while (!isEnd && (current == '*' || current == '/')) {
var op = current; var op = current;
eat(); eat();
var right = parseAtom(); var right = parsePow();
if (op == '*') { if (op == '*') {
expr = MulExpr(expr, right); expr = MulExpr(expr, right);
} else { } else {
@@ -49,6 +49,17 @@ class Parser {
return expr; return expr;
} }
Expr parsePow() {
var expr = parseAtom();
skipSpaces();
if (!isEnd && current == '^') {
eat();
var right = parsePow(); // right associative
return PowExpr(expr, right);
}
return expr;
}
Expr parseAtom() { Expr parseAtom() {
skipSpaces(); skipSpaces();
if (current == '(') { if (current == '(') {
@@ -106,13 +117,20 @@ class Parser {
return VarExpr(varName); return VarExpr(varName);
} }
// 解析 // 解析数字 (整数或小数)
var buf = ''; var buf = '';
while (!isEnd && RegExp(r'\d').hasMatch(current)) { bool hasDot = false;
while (!isEnd &&
(RegExp(r'\d').hasMatch(current) || (!hasDot && current == '.'))) {
if (current == '.') hasDot = true;
buf += current; buf += current;
eat(); eat();
} }
if (buf.isEmpty) throw Exception("无法解析: $current"); if (buf.isEmpty) throw Exception("无法解析: $current");
if (hasDot) {
return DoubleExpr(double.parse(buf));
} else {
return IntExpr(int.parse(buf)); return IntExpr(int.parse(buf));
} }
} }
}

View File

@@ -52,12 +52,17 @@ class SolverService {
/// 1. 求解简单表达式 /// 1. 求解简单表达式
CalculationResult _solveSimpleExpression(String input) { CalculationResult _solveSimpleExpression(String input) {
final steps = <CalculationStep>[]; final steps = <CalculationStep>[];
// Parse the input to get LaTeX-formatted version
final parser = Parser(input);
final parsedExpr = parser.parse();
final latexInput = parsedExpr.toString().replaceAll('*', '\\cdot');
steps.add( steps.add(
CalculationStep( CalculationStep(
stepNumber: 1, stepNumber: 1,
title: '表达式求值', title: '表达式求值',
explanation: '这是一个标准的数学表达式,我们将直接计算其结果。', explanation: '这是一个标准的数学表达式,我们将直接计算其结果。',
formula: '\$\$$input\$\$', formula: '\$\$$latexInput\$\$',
), ),
); );
@@ -113,12 +118,17 @@ class SolverService {
/// 2. 求解一元一次方程 /// 2. 求解一元一次方程
CalculationResult _solveLinearEquation(String input) { CalculationResult _solveLinearEquation(String input) {
final steps = <CalculationStep>[]; final steps = <CalculationStep>[];
// Parse the input to get LaTeX-formatted version
final parser = Parser(input);
final parsedExpr = parser.parse();
final latexInput = parsedExpr.toString().replaceAll('*', '\\cdot');
steps.add( steps.add(
CalculationStep( CalculationStep(
stepNumber: 0, stepNumber: 0,
title: '原方程', title: '原方程',
explanation: '这是一元一次方程。', explanation: '这是一元一次方程。',
formula: '\$\$$input\$\$', formula: '\$\$$latexInput\$\$',
), ),
); );
@@ -1262,8 +1272,7 @@ ${b1}y &= ${c1 - a1 * x.toDouble()}
/// 格式化原始方程,保持符号形式 /// 格式化原始方程,保持符号形式
String _formatOriginalEquation(String input) { String _formatOriginalEquation(String input) {
// Simply return the original equation with proper LaTeX formatting // Parse the equation and convert to LaTeX
// This avoids complex parsing issues and preserves the original symbolic form
String result = input.replaceAll(' ', ''); String result = input.replaceAll(' ', '');
// 确保方程格式正确 // 确保方程格式正确
@@ -1271,9 +1280,31 @@ ${b1}y &= ${c1 - a1 * x.toDouble()}
result = '$result=0'; result = '$result=0';
} }
// Replace sqrt with LaTeX format final parts = result.split('=');
if (parts.length == 2) {
try {
final leftParser = Parser(parts[0]);
final leftExpr = leftParser.parse();
final rightParser = Parser(parts[1]);
final rightExpr = rightParser.parse();
result =
'${leftExpr.toString().replaceAll('*', '\\cdot')}=${rightExpr.toString().replaceAll('*', '\\cdot')}';
} catch (e) {
// Fallback to original if parsing fails
result = result.replaceAll('sqrt(', '\\sqrt{'); result = result.replaceAll('sqrt(', '\\sqrt{');
result = result.replaceAll(')', '}'); result = result.replaceAll(')', '}');
}
} else {
try {
final parser = Parser(result.split('=')[0]);
final expr = parser.parse();
result = '${expr.toString().replaceAll('*', '\\cdot')}=0';
} catch (e) {
// Fallback
result = result.replaceAll('sqrt(', '\\sqrt{');
result = result.replaceAll(')', '}');
}
}
return '\$\$$result\$\$'; return '\$\$$result\$\$';
} }

View File

@@ -39,7 +39,7 @@ void main() {
test('非完全平方数', () { test('非完全平方数', () {
var expr = Parser("sqrt(8)").parse(); var expr = Parser("sqrt(8)").parse();
expect(expr.simplify().toString().replaceAll(' ', ''), "(2*sqrt(2))"); expect(expr.simplify().toString().replaceAll(' ', ''), "(2*\\sqrt{2})");
}); });
}); });
@@ -53,7 +53,7 @@ void main() {
var expr = Parser("sqrt(8)/4 + 1/2").parse(); var expr = Parser("sqrt(8)/4 + 1/2").parse();
expect( expect(
expr.evaluate().toString().replaceAll(' ', ''), expr.evaluate().toString().replaceAll(' ', ''),
"((sqrt(2)/2)+1/2)", "((\\sqrt{2}/2)+1/2)",
); );
}); });
}); });