目前共有2篇帖子。 內容轉換:不轉換▼
 
點擊 回復
305 1
【备份】算式解析代码
一派護法 十九級
1樓 發表于:2016-6-14 22:12
/** 功能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_notation
double 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_algorithm
UINT 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; // 默认左结合, 最高优先级
    }
}
一派護法 十九級
2樓 發表于:2016-6-14 22:13
#define isfn(ch) ((ch) == '_' || ((ch) >= 'A' && (ch) <= 'Z') || ((ch) >= 'a' && (ch) <= 'z'))
#define isleftbrac(ch) ((ch) == '(' || (ch) == '[' || (ch) == '{')
#define isnum(ch) (((ch) >= '0' && (ch) <= '9') || (ch) == '.') // 判断字符是否为数字或小数点
#define isop(str) (oplen(str) > 0)

#define max(a, b) (((a) > (b)) ? (a) : (b))
#define min(a, b) (((a) < (b)) ? (a) : (b))

#define fnjmp(dest, src) jmp((dest), (src), fnlen(*src))
#define opjmp(dest, src) jmp((dest), (src), oplen(*src))

#define OP_PREC(code) ((code) >> 4)
#define OP_RIGHTASSOCIATIVE 0x08

void calc_start(void);
double evaluate(const char *exp);
BOOL fncmp(const char *str, const char *src);
int fnlen(const char *fn);
BOOL jmp(char **dest, const char **src, int len);
int oplen(const char *op);
int paramcnt(const char *fn);
UINT parse(const char *str, char *buf);
UINT precedence(const char *op);

回復帖子

內容:
用戶名: 您目前是匿名發表
驗證碼:
(快捷鍵:Ctrl+Enter)
 

本帖信息

點擊數:305 回複數:1
評論數: ?
作者: 巨大八爪鱼
最後回復:巨大八爪鱼
最後回復時間:2016-6-14 22:13
 
©2010-2024 Arslanbar Ver2.0
除非另有聲明,本站採用創用CC姓名標示-相同方式分享 3.0 Unported許可協議進行許可。