From ebe9f89c9b6845ca80d0ebcbd5e89af6cd7d36d9 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 14 Sep 2025 13:50:23 +0800 Subject: [PATCH] :recycle: Move the sin / cos / tan to the calcualtor --- lib/calculator.dart | 155 +++++++++++++++++++++++++++++ lib/parser.dart | 38 ++++++++ lib/solver.dart | 198 +------------------------------------- test/calculator_test.dart | 130 +++++++++++++++++++++++++ 4 files changed, 326 insertions(+), 195 deletions(-) diff --git a/lib/calculator.dart b/lib/calculator.dart index 53c221a..190d132 100644 --- a/lib/calculator.dart +++ b/lib/calculator.dart @@ -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); diff --git a/lib/parser.dart b/lib/parser.dart index 45773c4..bd7b9c0 100644 --- a/lib/parser.dart +++ b/lib/parser.dart @@ -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; +} diff --git a/lib/solver.dart b/lib/solver.dart index 534965e..53da5d4 100644 --- a/lib/solver.dart +++ b/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 diff --git a/test/calculator_test.dart b/test/calculator_test.dart index 555d6b9..988acb2 100644 --- a/test/calculator_test.dart +++ b/test/calculator_test.dart @@ -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))', + ); + }); + }); }