/** 功能1: 算式计算 **/
#include <errno.h>
#include <math.h>
#include <stdio.h>
#include "common.h"
#include "calc.h"
void calc_start(void)
{
char str[MAXLEN + 1];
char exp[MAXLEN + 1];
double value;
FILE *fp;
printf("请输入要计算的数学算式 (不多于%d字符): ", MAXLEN);
input(str, sizeof(str)); // 输入算式
parse(str, exp); // 将算式转换为逆波兰表达式
if (!errno)
value = evaluate(exp); // 求逆波兰表达式的值
switch (errno)
{
case NOERR:
printf("计算结果为: %g\n", value);
fp = fopen(FILENAME, "a");
if (fp != NULL)
{
fprintf(fp, "%s=%g\n", str, value);
fclose(fp);
}
else
puts("警告: 保存计算结果失败");
break;
case ERR_MOD:
puts("表达式有误: 只能对两个整数取模");
break;
case ERR_ZERODIV:
puts("表达式有误: 除数不能为0");
break;
case ERR_UDEFOP:
puts("表达式有误: 含有无法识别的运算符");
break;
case ERR_UDEFFUN:
puts("表达式有误: 含有无法识别的函数名");
break;
case ERR_BRACMISMATCH:
puts("表达式有误: 括号不匹配");
break;
case ERR_INVALIDCOMMA:
puts("表达式有误: 无效的逗号");
break;
case ERR_INVALID:
puts("表达式不合法");
break;
case ERR_PARAMCNTMISMATCH:
puts("表达式有误: 函数参数个数不正确");
break;
case EDOM:
puts("表达式有误: 幂运算的运算数不合法");
break;
case ERANGE:
puts("计算失败: 幂运算结果太大或太小, 超出了运算能力");
break;
default:
puts("计算失败: 未知错误");
}
}
// 计算逆波兰表达式(RPN)的值
// 参考资料:
https://en.wikipedia.org/wiki/Reverse_Polish_notationdouble evaluate(const char *exp)
{
double stack[MAXLEN];
int i = 0; // 栈指针
int n; // sscanf读取的字符数
errno = NOERR;
while (*exp != '\0')
{
if (sscanf(exp, "%lf%n", stack + i, &n) != 1)
{
// 遇到运算符就立即从栈中弹出两个数并作运算
// 然后丢弃stack[i]的值
while (*exp == ' ')
exp++; // 跳过空格
if (*exp == '\0')
break;
/* 单目运算符或函数 */
if (i <= 0)
return errno = ERR_INVALID; // 单目运算符至少需要一个运算数
if (fncmp(exp, "sin"))
stack[i - 1] = sin(stack[i - 1]);
else if (fncmp(exp, "cos"))
stack[i - 1] = cos(stack[i - 1]);
else if (fncmp(exp, "tan"))
stack[i - 1] = tan(stack[i - 1]);
else if (fncmp(exp, "cot"))
{
stack[i - 1] = tan(stack[i - 1]);
if (stack[i - 1] == 0.0)
errno = ERR_INVALID;
else
stack[i - 1] = 1 / stack[i - 1];
}
else
{
/* 双目运算符或函数 */
i--;
if (i <= 0)
return errno = ERR_INVALID; // 双目运算符至少需要两个运算数
switch (*exp)
{
case '+':
stack[i - 1] += stack[i];
break;
case '-':
stack[i - 1] -= stack[i];
break;
case 'x':
case '*':
stack[i - 1] *= stack[i];
break;
case '/':
if (stack[i] != 0.0)
stack[i - 1] /= stack[i];
else
return errno = ERR_ZERODIV;
break;
case '%':
if (stack[i] == (int)stack[i] && stack[i - 1] == (int)stack[i - 1])
stack[i - 1] = (int)stack[i - 1] % (int)stack[i];
else
return errno = ERR_MOD;
break;
case '^':
stack[i - 1] = pow(stack[i - 1], stack[i]);
if (errno)
return errno;
break;
default:
if (fncmp(exp, "max"))
stack[i - 1] = max(stack[i - 1], stack[i]);
else if (fncmp(exp, "min"))
stack[i - 1] = min(stack[i - 1], stack[i]);
else
return errno = ERR_INVALID;
}
}
if (!opjmp(NULL, &exp))
fnjmp(NULL, &exp);
}
else
{
i++; // 遇到数就将数入栈
exp += n; // 字符指针前进
}
}
if (i != 1)
return errno = ERR_INVALID; // 最终栈中只能有且只有一个数据
return stack[0]; // 将栈中最后一个数作为计算结果返回
}
// 判断str函数名是否和src相同
// src必须以\0结束, 但str不需要
BOOL fncmp(const char *str, const char *src)
{
while (*src != '\0')
{
if (*str != *src)
return FALSE;
str++;
src++;
}
return (BOOL)(!isfn(*str));
}
// 求函数名长度
int fnlen(const char *fn)
{
int n = 0;
while (isfn(*fn)) // 宏调用中不能出现自增自减运算符
{
fn++;
n++;
}
return n;
}
// 跳过或复制指定数量的字符, 并在末尾添加空格
BOOL jmp(char **dest, const char **src, int len)
{
if (len <= 0)
return FALSE;
if (dest != NULL)
{
while (len--)
*(*dest)++ = *(*src)++;
*(*dest)++ = ' ';
}
else
*src += len;
return TRUE;
}
// 求运算符的长度
int oplen(const char *op)
{
switch (*op)
{
case '+':
case '-':
case '*':
case 'x':
case '/':
case '%':
case '^':
return 1;
default:
return -1; // 未知长度
}
}
// 求函数的参数个数
int paramcnt(const char *fn)
{
if (fncmp(fn, "sin") || fncmp(fn, "cos") || fncmp(fn, "tan") || fncmp(fn, "cot"))
return 1;
else if (fncmp(fn, "max") || fncmp(fn, "min"))
return 2;
else
return -1; // 函数不存在
}
// 将数学表达式转换为逆波兰表达式
// 参考资料:
https://en.wikipedia.org/wiki/Shunting-yard_algorithmUINT parse(const char *str, char *buf)
{
BOOL addzero = TRUE; // 是否需要添0
char left;
const char *cp; // 临时用字符串指针
const char *stack[MAXLEN]; // 运算符及函数名栈, 栈中运算符的优先级必须是从低到高
int comma_cnt = 0; // 逗号个数
int i = 0; // 栈指针
UINT prec, prec2; // 运算符的优先级码
errno = NOERR;
while (*str != '\0')
{
while (*str == ' ')
str++; // 跳过空格
if (isnum(*str))
{
// 如果读到数字,则直接输出
do
{
*buf++ = *str++;
} while (isnum(*str));
*buf++ = ' ';
addzero = FALSE;
}
else if (isleftbrac(*str))
{
// 如果读到左括号, 则无条件入栈
stack[i++] = str++;
addzero = TRUE;
}
else if (*str == ')' || *str == ']' || *str == '}')
{
// 如果读到右括号
switch (*str)
{
case ')':
left = '(';
break;
case ']':
left = '[';
break;
case '}':
left = '{';
break;
}
// ch为对应的左括号
comma_cnt = 0;
while (i > 0 && *stack[i - 1] != left)
{
// 弹出所有运算符和函数名
cp = stack[--i];
if (*cp == ',')
comma_cnt++;
else if (!opjmp(&buf, &cp))
fnjmp(&buf, &cp);
}
if (i > 0)
{
i--; // 括号匹配, 左括号出栈
while (i > 0)
{
cp = stack[i - 1];
if (fnjmp(&buf, &cp))
{
i--; // 如果顶层是函数名, 则函数名出栈
if (paramcnt(stack[i]) != comma_cnt + 1)
return errno = ERR_PARAMCNTMISMATCH; // 函数参数个数不匹配, 退出
}
else
break;
}
}
else
return errno = ERR_BRACMISMATCH; // 括号不匹配, 退出
str++;
}
else if (*str == ',')
{
// 如果读到逗号
while (i > 0 && !isleftbrac(stack[i - 1][0]))
{
// 弹出最近括号后的所有运算符
cp = stack[--i];
if (!opjmp(&buf, &cp))
fnjmp(&buf, &cp);
}
if (i <= 0)
return errno = ERR_INVALIDCOMMA;
stack[i++] = str++; // 逗号入栈, 便于检查函数参数个数是否正确
addzero = TRUE;
}
else if (isop(str))
{
// 如果读到运算符
prec = precedence(str);
while (i > 0)
{
if (isop(stack[i - 1]))
{
// 如果栈顶是运算符
prec2 = precedence(stack[i - 1]);
if ((!(prec & OP_RIGHTASSOCIATIVE) && OP_PREC(prec) <= OP_PREC(prec2)) || ((prec & OP_RIGHTASSOCIATIVE) && OP_PREC(prec) < OP_PREC(prec2))) // 比较两个运算符的优先级
{
// 不断地弹出栈顶的更高优先级运算符
cp = stack[--i];
opjmp(&buf, &cp);
}
else
break; // 优先级更高的运算符已经全部弹出了, 此时该运算符可以入栈了
}
else if (isfn(stack[i - 1][0]))
{
// 遇到运算符时, 栈中的函数名无条件弹出
cp = stack[--i];
if (fnjmp(&buf, &cp))
{
if (paramcnt(stack[i]) != 1)
return errno = ERR_PARAMCNTMISMATCH; // 不使用括号时, 函数参数个数必须为1
}
}
else
break; // 如果栈顶是括号、逗号或其他内容, 则不弹出
}
if (addzero && (*str == '+' || *str == '-'))
{
// 最左边出现符号, 则添0
*buf++ = '0';
*buf++ = ' ';
addzero = FALSE;
}
// 将运算符入栈
stack[i++] = str;
if (!opjmp(NULL, &str)) // 跳过运算符剩下的字符, 防止入栈
return errno = ERR_UDEFOP; // 运算符不存在, 退出
}
else if (isfn(*str))
{
// 如果读到函数名, 则无条件将指向该函数名首字符的指针入栈
if (paramcnt(str) == -1)
return errno = ERR_UDEFFUN; // 函数不存在, 退出
stack[i++] = str;
fnjmp(NULL, &str);
}
else
return errno = ERR_UDEFOP;
}
// 将栈中剩余的内容全部弹出
while (i > 0)
{
cp = stack[--i];
if (isleftbrac(*cp)) // 剩余内容中不能有多余的括号
return errno = ERR_BRACMISMATCH;
if (!opjmp(&buf, &cp))
{
if (fnjmp(&buf, &cp))
{
if (paramcnt(stack[i]) != 1)
return errno = ERR_PARAMCNTMISMATCH; // 不使用括号时, 函数参数个数必须为1
}
else
return errno = ERR_INVALID;
}
}
*buf = '\0';
return NOERR;
}
// 求运算符的优先级码
// 返回值高四位表示优先级, 低四位的最高位表示是左结合(0)还是右结合(1)
UINT precedence(const char *op)
{
switch (*op)
{
case '+':
return 0x20;
case '-':
return 0x21;
case '*':
case 'x':
return 0x30;
case '/':
return 0x31;
case '%':
return 0x32;
case '^':
return 0x48;
default:
return 0xf0; // 默认左结合, 最高优先级
}
}