/** 功能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; // 默認左結合, 最高優先級
}
}