♻️ Move the sin / cos / tan to the calcualtor
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
// === 在 abstract class Expr 中添加声明 ===
|
||||
import 'dart:math' show sqrt, cos, sin, tan, pow;
|
||||
import 'parser.dart';
|
||||
|
||||
abstract class Expr {
|
||||
Expr simplify();
|
||||
@@ -689,5 +690,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);
|
||||
|
@@ -134,3 +134,41 @@ class Parser {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 计算角度表达式(如 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;
|
||||
}
|
||||
|
198
lib/solver.dart
198
lib/solver.dart
@@ -67,7 +67,7 @@ class SolverService {
|
||||
);
|
||||
|
||||
// 检查是否为特殊三角函数值,可以返回精确结果
|
||||
final exactTrigResult = _getExactTrigResult(input);
|
||||
final exactTrigResult = getExactTrigResult(input);
|
||||
if (exactTrigResult != null) {
|
||||
return CalculationResult(
|
||||
steps: steps,
|
||||
@@ -76,7 +76,7 @@ class SolverService {
|
||||
}
|
||||
|
||||
// 预处理输入,将三角函数的参数从度转换为弧度
|
||||
String processedInput = _convertTrigToRadians(input);
|
||||
String processedInput = convertTrigToRadians(input);
|
||||
|
||||
try {
|
||||
// 使用自定义解析器解析表达式
|
||||
@@ -104,7 +104,7 @@ class SolverService {
|
||||
}
|
||||
|
||||
// 尝试将结果格式化为几倍根号的形式
|
||||
final formattedResult = _formatSqrtResult(result);
|
||||
final formattedResult = formatSqrtResult(result);
|
||||
|
||||
return CalculationResult(
|
||||
steps: steps,
|
||||
@@ -513,198 +513,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
|
||||
|
@@ -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() {
|
||||
@@ -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))',
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user