Compare commits

..

6 Commits

Author SHA1 Message Date
50857f2d2e Add LogExpr, ExpExpr and etc 2025-09-14 13:57:47 +08:00
ebe9f89c9b ♻️ Move the sin / cos / tan to the calcualtor 2025-09-14 13:50:23 +08:00
e6a52b8b74 Support calculate ^0.5 2025-09-14 13:42:14 +08:00
c9190d05a1 💄 Optimize graph painting 2025-09-14 13:28:11 +08:00
40dc6f8511 🗑️ Remove math_expressions as dependecy 2025-09-14 13:20:21 +08:00
2a56a83898 ♻️ Replace the math expressions with own calculator 2025-09-14 13:19:56 +08:00
9 changed files with 852 additions and 285 deletions

View File

@@ -1,5 +1,6 @@
// === 在 abstract class Expr 中添加声明 ===
import 'dart:math' show sqrt, cos, sin, tan;
import 'dart:math' show sqrt, cos, sin, tan, pow, log, exp, asin, acos, atan;
import 'parser.dart';
abstract class Expr {
Expr simplify();
@@ -7,6 +8,9 @@ abstract class Expr {
/// 新增:对表达式进行“求值/数值化”——尽可能把可算的部分算出来
Expr evaluate();
/// Substitute variable with value
Expr substitute(String varName, Expr value);
@override
String toString();
@@ -27,6 +31,9 @@ class IntExpr extends Expr {
@override
Expr evaluate() => this;
@override
Expr substitute(String varName, Expr value) => this;
@override
String toString() => value.toString();
}
@@ -42,10 +49,31 @@ class DoubleExpr extends Expr {
@override
Expr evaluate() => this;
@override
Expr substitute(String varName, Expr value) => this;
@override
String toString() => value.toString();
}
// === VarExpr ===
class VarExpr extends Expr {
final String name;
VarExpr(this.name);
@override
Expr simplify() => this;
@override
Expr evaluate() => this;
@override
Expr substitute(String varName, Expr value) => name == varName ? value : this;
@override
String toString() => name;
}
// === FractionExpr.evaluate ===
class FractionExpr extends Expr {
final int numerator;
@@ -74,6 +102,9 @@ class FractionExpr extends Expr {
@override
Expr evaluate() => simplify();
@override
Expr substitute(String varName, Expr value) => this;
@override
String toString() => "$numerator/$denominator";
}
@@ -150,6 +181,12 @@ class AddExpr extends Expr {
return AddExpr(l, r);
}
@override
Expr substitute(String varName, Expr value) => AddExpr(
left.substitute(varName, value),
right.substitute(varName, value),
);
@override
String toString() => "($left + $right)";
}
@@ -213,6 +250,12 @@ class SubExpr extends Expr {
return SubExpr(l, r);
}
@override
Expr substitute(String varName, Expr value) => SubExpr(
left.substitute(varName, value),
right.substitute(varName, value),
);
@override
String toString() => "($left - $right)";
}
@@ -296,6 +339,12 @@ class MulExpr extends Expr {
return MulExpr(l, r);
}
@override
Expr substitute(String varName, Expr value) => MulExpr(
left.substitute(varName, value),
right.substitute(varName, value),
);
@override
String toString() => "($left * $right)";
}
@@ -378,6 +427,12 @@ class DivExpr extends Expr {
return DivExpr(l, r);
}
@override
Expr substitute(String varName, Expr value) => DivExpr(
left.substitute(varName, value),
right.substitute(varName, value),
);
@override
String toString() => "($left / $right)";
}
@@ -430,7 +485,11 @@ class SqrtExpr extends Expr {
}
@override
String toString() => "sqrt($inner)";
Expr substitute(String varName, Expr value) =>
SqrtExpr(inner.substitute(varName, value));
@override
String toString() => "\\sqrt{${inner.toString()}}";
}
// === CosExpr ===
@@ -456,6 +515,10 @@ class CosExpr extends Expr {
return CosExpr(i);
}
@override
Expr substitute(String varName, Expr value) =>
CosExpr(inner.substitute(varName, value));
@override
String toString() => "cos($inner)";
}
@@ -483,6 +546,10 @@ class SinExpr extends Expr {
return SinExpr(i);
}
@override
Expr substitute(String varName, Expr value) =>
SinExpr(inner.substitute(varName, value));
@override
String toString() => "sin($inner)";
}
@@ -510,10 +577,285 @@ class TanExpr extends Expr {
return TanExpr(i);
}
@override
Expr substitute(String varName, Expr value) =>
TanExpr(inner.substitute(varName, value));
@override
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}';
}
}
// === LogExpr ===
class LogExpr extends Expr {
final Expr inner;
LogExpr(this.inner);
@override
Expr simplify() => LogExpr(inner.simplify());
@override
Expr evaluate() {
var i = inner.evaluate();
if (i is IntExpr) {
return DoubleExpr(log(i.value.toDouble()));
}
if (i is FractionExpr) {
return DoubleExpr(log(i.numerator / i.denominator));
}
if (i is DoubleExpr) {
return DoubleExpr(log(i.value));
}
return LogExpr(i);
}
@override
Expr substitute(String varName, Expr value) =>
LogExpr(inner.substitute(varName, value));
@override
String toString() => "log($inner)";
}
// === ExpExpr ===
class ExpExpr extends Expr {
final Expr inner;
ExpExpr(this.inner);
@override
Expr simplify() => ExpExpr(inner.simplify());
@override
Expr evaluate() {
var i = inner.evaluate();
if (i is IntExpr) {
return DoubleExpr(exp(i.value.toDouble()));
}
if (i is FractionExpr) {
return DoubleExpr(exp(i.numerator / i.denominator));
}
if (i is DoubleExpr) {
return DoubleExpr(exp(i.value));
}
return ExpExpr(i);
}
@override
Expr substitute(String varName, Expr value) =>
ExpExpr(inner.substitute(varName, value));
@override
String toString() => "exp($inner)";
}
// === AsinExpr ===
class AsinExpr extends Expr {
final Expr inner;
AsinExpr(this.inner);
@override
Expr simplify() => AsinExpr(inner.simplify());
@override
Expr evaluate() {
var i = inner.evaluate();
if (i is IntExpr) {
return DoubleExpr(asin(i.value.toDouble()));
}
if (i is FractionExpr) {
return DoubleExpr(asin(i.numerator / i.denominator));
}
if (i is DoubleExpr) {
return DoubleExpr(asin(i.value));
}
return AsinExpr(i);
}
@override
Expr substitute(String varName, Expr value) =>
AsinExpr(inner.substitute(varName, value));
@override
String toString() => "asin($inner)";
}
// === AcosExpr ===
class AcosExpr extends Expr {
final Expr inner;
AcosExpr(this.inner);
@override
Expr simplify() => AcosExpr(inner.simplify());
@override
Expr evaluate() {
var i = inner.evaluate();
if (i is IntExpr) {
return DoubleExpr(acos(i.value.toDouble()));
}
if (i is FractionExpr) {
return DoubleExpr(acos(i.numerator / i.denominator));
}
if (i is DoubleExpr) {
return DoubleExpr(acos(i.value));
}
return AcosExpr(i);
}
@override
Expr substitute(String varName, Expr value) =>
AcosExpr(inner.substitute(varName, value));
@override
String toString() => "acos($inner)";
}
// === AtanExpr ===
class AtanExpr extends Expr {
final Expr inner;
AtanExpr(this.inner);
@override
Expr simplify() => AtanExpr(inner.simplify());
@override
Expr evaluate() {
var i = inner.evaluate();
if (i is IntExpr) {
return DoubleExpr(atan(i.value.toDouble()));
}
if (i is FractionExpr) {
return DoubleExpr(atan(i.numerator / i.denominator));
}
if (i is DoubleExpr) {
return DoubleExpr(atan(i.value));
}
return AtanExpr(i);
}
@override
Expr substitute(String varName, Expr value) =>
AtanExpr(inner.substitute(varName, value));
@override
String toString() => "atan($inner)";
}
// === AbsExpr ===
class AbsExpr extends Expr {
final Expr inner;
AbsExpr(this.inner);
@override
Expr simplify() => AbsExpr(inner.simplify());
@override
Expr evaluate() {
var i = inner.evaluate();
if (i is IntExpr) {
return IntExpr(i.value.abs());
}
if (i is FractionExpr) {
return FractionExpr(i.numerator.abs(), i.denominator);
}
if (i is DoubleExpr) {
return DoubleExpr(i.value.abs());
}
return AbsExpr(i);
}
@override
Expr substitute(String varName, Expr value) =>
AbsExpr(inner.substitute(varName, value));
@override
String toString() => "|$inner|";
}
// === 辅助:识别 a * sqrt(X) 形式 ===
class _SqrtTerm {
final int coef;
@@ -535,5 +877,159 @@ _SqrtTerm? _asSqrtTerm(Expr e) {
return null;
}
/// 获取精确三角函数结果
String? getExactTrigResult(String input) {
final cleanInput = input.replaceAll(' ', '').toLowerCase();
// 匹配 sin(角度) 模式
final sinMatch = RegExp(r'^sin\((\d+(?:\+\d+)*)\)$').firstMatch(cleanInput);
if (sinMatch != null) {
final angleExpr = sinMatch.group(1)!;
final angle = evaluateAngleExpression(angleExpr);
if (angle != null) {
return getSinExactValue(angle);
}
}
// 匹配 cos(角度) 模式
final cosMatch = RegExp(r'^cos\((\d+(?:\+\d+)*)\)$').firstMatch(cleanInput);
if (cosMatch != null) {
final angleExpr = cosMatch.group(1)!;
final angle = evaluateAngleExpression(angleExpr);
if (angle != null) {
return getCosExactValue(angle);
}
}
// 匹配 tan(角度) 模式
final tanMatch = RegExp(r'^tan\((\d+(?:\+\d+)*)\)$').firstMatch(cleanInput);
if (tanMatch != null) {
final angleExpr = tanMatch.group(1)!;
final angle = evaluateAngleExpression(angleExpr);
if (angle != null) {
return getTanExactValue(angle);
}
}
return null;
}
/// 获取 sin 的精确值
String? getSinExactValue(int angle) {
// 标准化角度到 0-360 度
final normalizedAngle = angle % 360;
switch (normalizedAngle) {
case 0:
case 360:
return '0';
case 30:
return '\\frac{1}{2}';
case 45:
return '\\frac{\\sqrt{2}}{2}';
case 60:
return '\\frac{\\sqrt{3}}{2}';
case 75:
return '1 + \\frac{\\sqrt{2}}{2}';
case 90:
return '1';
case 120:
return '\\frac{\\sqrt{3}}{2}';
case 135:
return '\\frac{\\sqrt{2}}{2}';
case 150:
return '\\frac{1}{2}';
case 180:
return '0';
case 210:
return '-\\frac{1}{2}';
case 225:
return '-\\frac{\\sqrt{2}}{2}';
case 240:
return '-\\frac{\\sqrt{3}}{2}';
case 270:
return '-1';
case 300:
return '-\\frac{\\sqrt{3}}{2}';
case 315:
return '-\\frac{\\sqrt{2}}{2}';
case 330:
return '-\\frac{1}{2}';
default:
return null;
}
}
/// 获取 cos 的精确值
String? getCosExactValue(int angle) {
// cos(angle) = sin(90 - angle)
final complementaryAngle = 90 - angle;
return getSinExactValue(complementaryAngle.abs());
}
/// 获取 tan 的精确值
String? getTanExactValue(int angle) {
// tan(angle) = sin(angle) / cos(angle)
final sinValue = getSinExactValue(angle);
final cosValue = getCosExactValue(angle);
if (sinValue != null && cosValue != null) {
if (cosValue == '0') return null; // 未定义
return '\\frac{$sinValue}{$cosValue}';
}
return null;
}
/// 将数值结果格式化为几倍根号的形式
String formatSqrtResult(double result) {
// 处理负数
if (result < 0) {
return '-${formatSqrtResult(-result)}';
}
// 处理零
if (result == 0) return '0';
// 检查是否接近整数
final rounded = result.round();
if ((result - rounded).abs() < 1e-10) {
return rounded.toString();
}
// 计算 result 的平方,看它是否接近整数
final squared = result * result;
final squaredRounded = squared.round();
// 如果 squared 接近整数,说明 result 是某个数的平方根
if ((squared - squaredRounded).abs() < 1e-6) {
// 寻找最大的完全平方数因子
int maxSquareFactor = 1;
for (int i = 2; i * i <= squaredRounded; i++) {
if (squaredRounded % (i * i) == 0) {
maxSquareFactor = i * i;
}
}
final coefficient = sqrt(maxSquareFactor).round();
final remaining = squaredRounded ~/ maxSquareFactor;
if (remaining == 1) {
// 完全平方数,直接返回系数
return coefficient.toString();
} else if (coefficient == 1) {
return '\\sqrt{$remaining}';
} else {
return '$coefficient\\sqrt{$remaining}';
}
}
// 如果不是平方根的结果,返回原始数值(保留几位小数)
return result
.toStringAsFixed(6)
.replaceAll(RegExp(r'\.0+$'), '')
.replaceAll(RegExp(r'\.$'), '');
}
/// 辗转相除法求 gcd
int _gcd(int a, int b) => b == 0 ? a : _gcd(b, a % b);

View File

@@ -33,12 +33,12 @@ class Parser {
}
Expr parseMul() {
var expr = parseAtom();
var expr = parsePow();
skipSpaces();
while (!isEnd && (current == '*' || current == '/')) {
var op = current;
eat();
var right = parseAtom();
var right = parsePow();
if (op == '*') {
expr = MulExpr(expr, right);
} else {
@@ -49,63 +49,171 @@ class Parser {
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() {
skipSpaces();
bool negative = false;
if (current == '-') {
negative = true;
eat();
skipSpaces();
}
Expr expr;
if (current == '(') {
eat();
var expr = parse();
expr = parse();
if (current != ')') throw Exception("缺少 )");
eat();
return expr;
}
if (input.startsWith("sqrt", pos)) {
} else if (input.startsWith("sqrt", pos)) {
pos += 4;
if (current != '(') throw Exception("sqrt 缺少 (");
eat();
var inner = parse();
if (current != ')') throw Exception("sqrt 缺少 )");
eat();
return SqrtExpr(inner);
}
if (input.startsWith("cos", pos)) {
expr = SqrtExpr(inner);
} else if (input.startsWith("cos", pos)) {
pos += 3;
if (current != '(') throw Exception("cos 缺少 (");
eat();
var inner = parse();
if (current != ')') throw Exception("cos 缺少 )");
eat();
return CosExpr(inner);
}
if (input.startsWith("sin", pos)) {
expr = CosExpr(inner);
} else if (input.startsWith("sin", pos)) {
pos += 3;
if (current != '(') throw Exception("sin 缺少 (");
eat();
var inner = parse();
if (current != ')') throw Exception("sin 缺少 )");
eat();
return SinExpr(inner);
}
if (input.startsWith("tan", pos)) {
expr = SinExpr(inner);
} else if (input.startsWith("tan", pos)) {
pos += 3;
if (current != '(') throw Exception("tan 缺少 (");
eat();
var inner = parse();
if (current != ')') throw Exception("tan 缺少 )");
eat();
return TanExpr(inner);
}
// 解析整数
var buf = '';
while (!isEnd && RegExp(r'\d').hasMatch(current)) {
buf += current;
expr = TanExpr(inner);
} else if (input.startsWith("log", pos)) {
pos += 3;
if (current != '(') throw Exception("log 缺少 (");
eat();
var inner = parse();
if (current != ')') throw Exception("log 缺少 )");
eat();
expr = LogExpr(inner);
} else if (input.startsWith("exp", pos)) {
pos += 3;
if (current != '(') throw Exception("exp 缺少 (");
eat();
var inner = parse();
if (current != ')') throw Exception("exp 缺少 )");
eat();
expr = ExpExpr(inner);
} else if (input.startsWith("asin", pos)) {
pos += 4;
if (current != '(') throw Exception("asin 缺少 (");
eat();
var inner = parse();
if (current != ')') throw Exception("asin 缺少 )");
eat();
expr = AsinExpr(inner);
} else if (input.startsWith("acos", pos)) {
pos += 4;
if (current != '(') throw Exception("acos 缺少 (");
eat();
var inner = parse();
if (current != ')') throw Exception("acos 缺少 )");
eat();
expr = AcosExpr(inner);
} else if (input.startsWith("atan", pos)) {
pos += 4;
if (current != '(') throw Exception("atan 缺少 (");
eat();
var inner = parse();
if (current != ')') throw Exception("atan 缺少 )");
eat();
expr = AtanExpr(inner);
} else if (current == '|') {
eat();
var inner = parse();
if (current != '|') throw Exception("abs 缺少 |");
eat();
expr = AbsExpr(inner);
} else if (RegExp(r'[a-zA-Z]').hasMatch(current)) {
var varName = current;
eat();
expr = VarExpr(varName);
} else {
// 解析数字 (整数或小数)
var buf = '';
bool hasDot = false;
while (!isEnd &&
(RegExp(r'\d').hasMatch(current) || (!hasDot && current == '.'))) {
if (current == '.') hasDot = true;
buf += current;
eat();
}
if (buf.isEmpty) throw Exception("无法解析: $current");
if (hasDot) {
expr = DoubleExpr(double.parse(buf));
} else {
expr = IntExpr(int.parse(buf));
}
}
if (buf.isEmpty) throw Exception("无法解析: $current");
return IntExpr(int.parse(buf));
if (negative) {
expr = SubExpr(IntExpr(0), expr);
}
return expr;
}
}
/// 计算角度表达式(如 30+45 = 75
int? evaluateAngleExpression(String expr) {
final parts = expr.split('+');
int sum = 0;
for (final part in parts) {
final num = int.tryParse(part.trim());
if (num == null) return null;
sum += num;
}
return sum;
}
/// 将三角函数的参数从度转换为弧度
String convertTrigToRadians(String input) {
String result = input;
// 正则表达式匹配三角函数调用,如 sin(30), cos(45), tan(60)
final trigPattern = RegExp(
r'(sin|cos|tan|asin|acos|atan)\s*\(\s*([^)]+)\s*\)',
caseSensitive: false,
);
result = result.replaceAllMapped(trigPattern, (match) {
final func = match.group(1)!;
final arg = match.group(2)!;
// 如果参数已经是弧度相关的表达式(包含 pi 或 π),则不转换
if (arg.contains('pi') || arg.contains('π') || arg.contains('rad')) {
return '$func($arg)';
}
// 将度数转换为弧度:度 * π / 180
return '$func(($arg)*(π/180))';
});
return result;
}

View File

@@ -3,7 +3,8 @@ import 'package:latext/latext.dart';
import 'package:simple_math_calc/models/calculation_step.dart';
import 'package:simple_math_calc/solver.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:math_expressions/math_expressions.dart' as math_expressions;
import 'package:simple_math_calc/calculator.dart';
import 'package:simple_math_calc/parser.dart';
import 'dart:math';
class CalculatorHomePage extends StatefulWidget {
@@ -70,11 +71,8 @@ class _CalculatorHomePageState extends State<CalculatorHomePage> {
);
// 解析表达式
final parser = math_expressions.ShuntingYardParser();
final expr = parser.parse(functionExpr);
// 创建变量 x
final x = math_expressions.Variable('x');
final parser = Parser(functionExpr);
final expr = parser.parse();
// 根据缩放因子动态调整范围和步长
final range = 10.0 * zoomFactor;
@@ -84,13 +82,15 @@ class _CalculatorHomePageState extends State<CalculatorHomePage> {
List<FlSpot> points = [];
for (double i = -range; i <= range; i += step) {
try {
final context = math_expressions.ContextModel()
..bindVariable(x, math_expressions.Number(i));
final evaluator = math_expressions.RealEvaluator(context);
final y = evaluator.evaluate(expr);
// 替换变量 x 为当前值
final substituted = expr.substitute('x', DoubleExpr(i));
final evaluated = substituted.evaluate();
if (y.isFinite && !y.isNaN) {
points.add(FlSpot(i, y.toDouble()));
if (evaluated is DoubleExpr) {
final y = evaluated.value;
if (y.isFinite && !y.isNaN) {
points.add(FlSpot(i, y));
}
}
} catch (e) {
// 跳过无法计算的点
@@ -410,12 +410,22 @@ class _CalculatorHomePageState extends State<CalculatorHomePage> {
sideTitles: SideTitles(
showTitles: true,
reservedSize: 40,
getTitlesWidget: (value, meta) =>
SideTitleWidget(
axisSide: meta.axisSide,
child: Text(value.toStringAsFixed(2)),
),
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 30,
getTitlesWidget: (value, meta) =>
SideTitleWidget(
axisSide: meta.axisSide,
child: Text(value.toStringAsFixed(2)),
),
),
),
topTitles: AxisTitles(
@@ -431,6 +441,19 @@ class _CalculatorHomePageState extends State<CalculatorHomePage> {
color: Theme.of(context).colorScheme.outline,
),
),
lineTouchData: LineTouchData(
enabled: true,
touchTooltipData: LineTouchTooltipData(
getTooltipItems: (touchedSpots) {
return touchedSpots.map((spot) {
return LineTooltipItem(
'x = ${spot.x.toStringAsFixed(2)}',
const TextStyle(color: Colors.white),
);
}).toList();
},
),
),
lineBarsData: [
LineChartBarData(
spots: points,

View File

@@ -1,4 +1,5 @@
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:rational/rational.dart';
import 'models/calculation_step.dart';
import 'calculator.dart';
@@ -52,17 +53,22 @@ class SolverService {
/// 1. 求解简单表达式
CalculationResult _solveSimpleExpression(String input) {
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(
CalculationStep(
stepNumber: 1,
title: '表达式求值',
explanation: '这是一个标准的数学表达式,我们将直接计算其结果。',
formula: '\$\$$input\$\$',
formula: '\$\$$latexInput\$\$',
),
);
// 检查是否为特殊三角函数值,可以返回精确结果
final exactTrigResult = _getExactTrigResult(input);
final exactTrigResult = getExactTrigResult(input);
if (exactTrigResult != null) {
return CalculationResult(
steps: steps,
@@ -71,7 +77,7 @@ class SolverService {
}
// 预处理输入,将三角函数的参数从度转换为弧度
String processedInput = _convertTrigToRadians(input);
String processedInput = convertTrigToRadians(input);
try {
// 使用自定义解析器解析表达式
@@ -99,7 +105,7 @@ class SolverService {
}
// 尝试将结果格式化为几倍根号的形式
final formattedResult = _formatSqrtResult(result);
final formattedResult = formatSqrtResult(result);
return CalculationResult(
steps: steps,
@@ -113,12 +119,17 @@ class SolverService {
/// 2. 求解一元一次方程
CalculationResult _solveLinearEquation(String input) {
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(
CalculationStep(
stepNumber: 0,
title: '原方程',
explanation: '这是一元一次方程。',
formula: '\$\$$input\$\$',
formula: '\$\$$latexInput\$\$',
),
);
@@ -503,198 +514,6 @@ ${b1}y &= ${c1 - a1 * x.toDouble()}
/// ---- 辅助函数 ----
/// 获取精确三角函数结果
String? _getExactTrigResult(String input) {
final cleanInput = input.replaceAll(' ', '').toLowerCase();
// 匹配 sin(角度) 模式
final sinMatch = RegExp(r'^sin\((\d+(?:\+\d+)*)\)$').firstMatch(cleanInput);
if (sinMatch != null) {
final angleExpr = sinMatch.group(1)!;
final angle = _evaluateAngleExpression(angleExpr);
if (angle != null) {
return _getSinExactValue(angle);
}
}
// 匹配 cos(角度) 模式
final cosMatch = RegExp(r'^cos\((\d+(?:\+\d+)*)\)$').firstMatch(cleanInput);
if (cosMatch != null) {
final angleExpr = cosMatch.group(1)!;
final angle = _evaluateAngleExpression(angleExpr);
if (angle != null) {
return _getCosExactValue(angle);
}
}
// 匹配 tan(角度) 模式
final tanMatch = RegExp(r'^tan\((\d+(?:\+\d+)*)\)$').firstMatch(cleanInput);
if (tanMatch != null) {
final angleExpr = tanMatch.group(1)!;
final angle = _evaluateAngleExpression(angleExpr);
if (angle != null) {
return _getTanExactValue(angle);
}
}
return null;
}
/// 计算角度表达式(如 30+45 = 75
int? _evaluateAngleExpression(String expr) {
final parts = expr.split('+');
int sum = 0;
for (final part in parts) {
final num = int.tryParse(part.trim());
if (num == null) return null;
sum += num;
}
return sum;
}
/// 获取 sin 的精确值
String? _getSinExactValue(int angle) {
// 标准化角度到 0-360 度
final normalizedAngle = angle % 360;
switch (normalizedAngle) {
case 0:
case 360:
return '0';
case 30:
return '\\frac{1}{2}';
case 45:
return '\\frac{\\sqrt{2}}{2}';
case 60:
return '\\frac{\\sqrt{3}}{2}';
case 75:
return '1 + \\frac{\\sqrt{2}}{2}';
case 90:
return '1';
case 120:
return '\\frac{\\sqrt{3}}{2}';
case 135:
return '\\frac{\\sqrt{2}}{2}';
case 150:
return '\\frac{1}{2}';
case 180:
return '0';
case 210:
return '-\\frac{1}{2}';
case 225:
return '-\\frac{\\sqrt{2}}{2}';
case 240:
return '-\\frac{\\sqrt{3}}{2}';
case 270:
return '-1';
case 300:
return '-\\frac{\\sqrt{3}}{2}';
case 315:
return '-\\frac{\\sqrt{2}}{2}';
case 330:
return '-\\frac{1}{2}';
default:
return null;
}
}
/// 获取 cos 的精确值
String? _getCosExactValue(int angle) {
// cos(angle) = sin(90 - angle)
final complementaryAngle = 90 - angle;
return _getSinExactValue(complementaryAngle.abs());
}
/// 获取 tan 的精确值
String? _getTanExactValue(int angle) {
// tan(angle) = sin(angle) / cos(angle)
final sinValue = _getSinExactValue(angle);
final cosValue = _getCosExactValue(angle);
if (sinValue != null && cosValue != null) {
if (cosValue == '0') return null; // 未定义
return '\\frac{$sinValue}{$cosValue}';
}
return null;
}
/// 将三角函数的参数从度转换为弧度
String _convertTrigToRadians(String input) {
String result = input;
// 正则表达式匹配三角函数调用,如 sin(30), cos(45), tan(60)
final trigPattern = RegExp(
r'(sin|cos|tan|asin|acos|atan)\s*\(\s*([^)]+)\s*\)',
caseSensitive: false,
);
result = result.replaceAllMapped(trigPattern, (match) {
final func = match.group(1)!;
final arg = match.group(2)!;
// 如果参数已经是弧度相关的表达式(包含 pi 或 π),则不转换
if (arg.contains('pi') || arg.contains('π') || arg.contains('rad')) {
return '$func($arg)';
}
// 将度数转换为弧度:度 * π / 180
return '$func(($arg)*($pi/180))';
});
return result;
}
/// 将数值结果格式化为几倍根号的形式
String _formatSqrtResult(double result) {
// 处理负数
if (result < 0) {
return '-${_formatSqrtResult(-result)}';
}
// 处理零
if (result == 0) return '0';
// 检查是否接近整数
final rounded = result.round();
if ((result - rounded).abs() < 1e-10) {
return rounded.toString();
}
// 计算 result 的平方,看它是否接近整数
final squared = result * result;
final squaredRounded = squared.round();
// 如果 squared 接近整数,说明 result 是某个数的平方根
if ((squared - squaredRounded).abs() < 1e-6) {
// 寻找最大的完全平方数因子
int maxSquareFactor = 1;
for (int i = 2; i * i <= squaredRounded; i++) {
if (squaredRounded % (i * i) == 0) {
maxSquareFactor = i * i;
}
}
final coefficient = sqrt(maxSquareFactor).round();
final remaining = squaredRounded ~/ maxSquareFactor;
if (remaining == 1) {
// 完全平方数,直接返回系数
return coefficient.toString();
} else if (coefficient == 1) {
return '\\sqrt{$remaining}';
} else {
return '$coefficient\\sqrt{$remaining}';
}
}
// 如果不是平方根的结果,返回原始数值(保留几位小数)
return result
.toStringAsFixed(6)
.replaceAll(RegExp(r'\.0+$'), '')
.replaceAll(RegExp(r'\.$'), '');
}
String _expandExpressions(String input) {
String result = input;
int maxIterations = 10; // Prevent infinite loops
@@ -735,26 +554,26 @@ ${b1}y &= ${c1 - a1 * x.toDouble()}
if (factorMulMatch != null) {
final factor1 = factorMulMatch.group(1)!;
final factor2 = factorMulMatch.group(2)!;
print('Expanding: ($factor1) * ($factor2)');
debugPrint('Expanding: ($factor1) * ($factor2)');
final coeffs1 = _parsePolynomial(factor1);
final coeffs2 = _parsePolynomial(factor2);
print('Coeffs1: $coeffs1, Coeffs2: $coeffs2');
debugPrint('Coeffs1: $coeffs1, Coeffs2: $coeffs2');
final a = coeffs1[1] ?? 0;
final b = coeffs1[0] ?? 0;
final c = coeffs2[1] ?? 0;
final d = coeffs2[0] ?? 0;
print('a=$a, b=$b, c=$c, d=$d');
debugPrint('a=$a, b=$b, c=$c, d=$d');
final newA = a * c;
final newB = a * d + b * c;
final newC = b * d;
print('newA=$newA, newB=$newB, newC=$newC');
debugPrint('newA=$newA, newB=$newB, newC=$newC');
final expanded =
'${newA}x^2${newB >= 0 ? '+' : ''}${newB}x${newC >= 0 ? '+' : ''}$newC';
print('Expanded result: $expanded');
debugPrint('Expanded result: $expanded');
result = result.replaceFirst(factorMulMatch.group(0)!, expanded);
iterationCount++;
@@ -1215,7 +1034,7 @@ ${b1}y &= ${c1 - a1 * x.toDouble()}
if (simplifiedB == 0) {
// Just the sqrt part with correct sign
return isPlus ? '$sqrtExpr' : '-$sqrtExpr';
return isPlus ? sqrtExpr : '-$sqrtExpr';
} else if (simplifiedB == 1) {
// +1 * sqrt part
return isPlus ? '1 + $sqrtExpr' : '1 - $sqrtExpr';
@@ -1234,11 +1053,11 @@ ${b1}y &= ${c1 - a1 * x.toDouble()}
}
} else {
// Cannot simplify, use fraction form
final bStr = b > 0 ? '${bInt}' : '(${bInt})';
final bStr = b > 0 ? '$bInt' : '($bInt)';
final signStr = isPlus ? '+' : '-';
final numerator = b > 0
? '-$bStr $signStr $sqrtExpr'
: '(${bInt}) $signStr $sqrtExpr';
: '($bInt) $signStr $sqrtExpr';
if (denominator == 2) {
return '\\frac{$numerator}{2}';
@@ -1262,8 +1081,7 @@ ${b1}y &= ${c1 - a1 * x.toDouble()}
/// 格式化原始方程,保持符号形式
String _formatOriginalEquation(String input) {
// Simply return the original equation with proper LaTeX formatting
// This avoids complex parsing issues and preserves the original symbolic form
// Parse the equation and convert to LaTeX
String result = input.replaceAll(' ', '');
// 确保方程格式正确
@@ -1271,9 +1089,31 @@ ${b1}y &= ${c1 - a1 * x.toDouble()}
result = '$result=0';
}
// Replace sqrt with LaTeX format
result = result.replaceAll('sqrt(', '\\sqrt{');
result = result.replaceAll(')', '}');
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(')', '}');
}
} 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\$\$';
}

View File

@@ -400,14 +400,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.11.1"
math_expressions:
dependency: "direct main"
description:
name: math_expressions
sha256: "2e1ceb974c2b1893c809a68c7005f1b63f7324db0add800a0e792b1ac8ff9f03"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
meta:
dependency: transitive
description:

View File

@@ -34,7 +34,6 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
math_expressions: ^3.1.0
latext: ^0.5.1
google_fonts: ^6.3.1
go_router: ^16.2.1

View File

@@ -1,4 +1,5 @@
import 'package:simple_math_calc/parser.dart';
import 'package:simple_math_calc/calculator.dart';
import 'package:test/test.dart';
void main() {
@@ -39,7 +40,7 @@ void main() {
test('非完全平方数', () {
var expr = Parser("sqrt(8)").parse();
expect(expr.simplify().toString().replaceAll(' ', ''), "(2*sqrt(2))");
expect(expr.simplify().toString().replaceAll(' ', ''), "(2*\\sqrt{2})");
});
});
@@ -53,7 +54,7 @@ void main() {
var expr = Parser("sqrt(8)/4 + 1/2").parse();
expect(
expr.evaluate().toString().replaceAll(' ', ''),
"((sqrt(2)/2)+1/2)",
"((\\sqrt{2}/2)+1/2)",
);
});
});
@@ -101,4 +102,133 @@ void main() {
expect(expr.evaluate().toString(), "0.0");
});
});
group('精确三角函数值', () {
test('getExactTrigResult - sin(30)', () {
expect(getExactTrigResult('sin(30)'), '\\frac{1}{2}');
});
test('getExactTrigResult - cos(45)', () {
expect(getExactTrigResult('cos(45)'), '\\frac{\\sqrt{2}}{2}');
});
test('getExactTrigResult - tan(60)', () {
expect(
getExactTrigResult('tan(60)'),
'\\frac{\\frac{\\sqrt{3}}{2}}{\\frac{1}{2}}',
);
});
test('getExactTrigResult - sin(30+45)', () {
expect(getExactTrigResult('sin(30+45)'), '1 + \\frac{\\sqrt{2}}{2}');
});
test('getExactTrigResult - 无效输入', () {
expect(getExactTrigResult('sin(25)'), isNull);
});
test('getSinExactValue - 各种角度', () {
expect(getSinExactValue(0), '0');
expect(getSinExactValue(30), '\\frac{1}{2}');
expect(getSinExactValue(45), '\\frac{\\sqrt{2}}{2}');
expect(getSinExactValue(90), '1');
expect(getSinExactValue(180), '0');
expect(getSinExactValue(270), '-1');
});
test('getCosExactValue - 各种角度', () {
expect(getCosExactValue(0), '1');
expect(getCosExactValue(30), '\\frac{\\sqrt{3}}{2}');
expect(getCosExactValue(45), '\\frac{\\sqrt{2}}{2}');
expect(getCosExactValue(90), '0');
expect(getCosExactValue(180), '1');
});
test('getTanExactValue - 各种角度', () {
expect(getTanExactValue(0), '\\frac{0}{1}');
expect(
getTanExactValue(30),
'\\frac{\\frac{1}{2}}{\\frac{\\sqrt{3}}{2}}',
);
expect(
getTanExactValue(45),
'\\frac{\\frac{\\sqrt{2}}{2}}{\\frac{\\sqrt{2}}{2}}',
);
expect(
getTanExactValue(60),
'\\frac{\\frac{\\sqrt{3}}{2}}{\\frac{1}{2}}',
);
});
test('evaluateAngleExpression - 简单求和', () {
expect(evaluateAngleExpression('30+45'), 75);
expect(evaluateAngleExpression('60+30'), 90);
expect(evaluateAngleExpression('90'), 90);
});
test('evaluateAngleExpression - 无效输入', () {
expect(evaluateAngleExpression('30+a'), isNull);
expect(evaluateAngleExpression(''), isNull);
});
});
group('平方根格式化', () {
test('formatSqrtResult - 整数', () {
expect(formatSqrtResult(4.0), '4');
expect(formatSqrtResult(9.0), '9');
});
test('formatSqrtResult - 完全平方根', () {
expect(formatSqrtResult(4.0), '4');
expect(formatSqrtResult(9.0), '9');
});
test('formatSqrtResult - 非完全平方根', () {
expect(formatSqrtResult(2.0), '2');
expect(formatSqrtResult(3.0), '3');
});
test('formatSqrtResult - 带系数的平方根', () {
expect(formatSqrtResult(8.0), '8');
expect(formatSqrtResult(18.0), '18');
expect(formatSqrtResult(12.0), '12');
});
test('formatSqrtResult - 负数', () {
expect(formatSqrtResult(-4.0), '-4');
expect(formatSqrtResult(-2.0), '-2');
});
test('formatSqrtResult - 零', () {
expect(formatSqrtResult(0.0), '0');
});
test('formatSqrtResult - 小数', () {
expect(formatSqrtResult(1.4142135623730951), '\\sqrt{2}');
});
});
group('三角函数转换', () {
test('convertTrigToRadians - 基本转换', () {
expect(convertTrigToRadians('sin(30)'), 'sin((30)*(π/180))');
expect(convertTrigToRadians('cos(45)'), 'cos((45)*(π/180))');
expect(convertTrigToRadians('tan(60)'), 'tan((60)*(π/180))');
});
test('convertTrigToRadians - 弧度输入不变', () {
expect(convertTrigToRadians('sin(π/2)'), 'sin(π/2)');
expect(convertTrigToRadians('cos(rad)'), 'cos(rad)');
});
test('convertTrigToRadians - 复杂表达式', () {
expect(convertTrigToRadians('sin(30+45)'), 'sin((30+45)*(π/180))');
});
test('convertTrigToRadians - 多个函数', () {
expect(
convertTrigToRadians('sin(30) + cos(45)'),
'sin((30)*(π/180)) + cos((45)*(π/180))',
);
});
});
}

View File

@@ -1,21 +0,0 @@
import 'lib/solver.dart';
void main() {
final solver = SolverService();
// Test the problematic case
final input = '(x+8)(x+1)=-12';
print('Input: $input');
try {
final result = solver.solve(input);
print('Result: ${result.finalAnswer}');
print('Steps:');
for (final step in result.steps) {
print('Step ${step.stepNumber}: ${step.title}');
print(' Formula: ${step.formula}');
}
} catch (e) {
print('Error: $e');
}
}

View File

@@ -16,7 +16,7 @@
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<meta name="description" content="A simple math calculator.">
<!-- iOS meta tags & icons -->
<meta name="mobile-web-app-capable" content="yes">