一派护法 十九级 |
#include <errno.h> #include <math.h> #include <stdio.h> #include <string.h> #include "calc.h"
int main(void) { char buf[MAXLEN + 1]; char str[MAXLEN + 1]; double result; FILE *fp; int i, state; int source, dest;
puts("--------- 李玉牌计算器 ---------"); puts("- 1. 算式计算 -"); puts("- 2. 进制转换 -"); puts("- 3. 输出大写 -"); puts("- 4. 格式化显示 -"); puts("- 5. 查看历史记录 -"); puts("--------------------------------"); printf("请选择: "); scanf("%d", &i); switch (i) { case 1: printf("请输入算式 (不多于%d字符): ", MAXLEN); getstr(str, sizeof(str)); errno = NOERR; result = calc(str, strlen(str)); switch (errno) { case NOERR: printf("计算结果为: %lf\n", result); /*fp = fopen(FILENAME, "a"); fprintf(fp, "%s=%lf\n", str, result); fclose(fp);*/ break; case ERR_INVALID: puts("输入的表达式有误, 无法计算"); break; case ERR_TOOBIG: puts("运算数超出了计算机运算范围, 无法计算"); break; } break; case 2: printf("请输入要转换的数: "); getstr(str, sizeof(str)); strcpy(buf, str);
printf("请输入原进制: "); scanf("%d", &source); if (!valid_radix(source)) break;
printf("请输入目标进制: "); scanf("%d", &dest); if (!valid_radix(dest)) break;
state = convert(str, source, dest); switch (state) { case NOERR: printf("转换的结果为: %s\n", str); fp = fopen(FILENAME, "a"); fprintf(fp, "%s由%d进制转换为%d进制后是: %s\n", buf, source, dest, str); fclose(fp); break; case ERR_INVALID: puts("要转换的数中含有无效字符, 转换失败"); break; case ERR_TOOBIG: puts("数字太大, 无法转换"); break; } break; case 3:
break; case 4: printf("请输入要格式化的数: "); getstr(str, sizeof(str)); if (format(str, buf)) { printf("转换结果: %s\n", buf); fp = fopen(FILENAME, "a"); fprintf(fp, "%s格式化后是: %s\n", str, buf); fclose(fp); } else puts("输入的数有误"); break; case 5: view_logs(); break; default: puts("输入错误"); } return 0; }
void addstr(char **dest, char *src) { while (*(*dest)++ = *src++); }
// 计算指定算式 double calc(char *str, int len) { int n; // n为sscanf读到的字符个数 double a, b; char op; if (sscanf(str, "%lf%n", &a, &n) <= 0) // 读取第一个数 return errno = ERR_INVALID; // 读取失败时退出 str += n; len -= n; // 可用字符
while ((op = get_operator(&str, &len)) != '\0') // 获取运算符, 如果运算符不为空就循环 { if (sscanf(str, "%lf%n", &b, &n) <= 0) // 读取另一个数 return errno = ERR_INVALID; str += n; len -= n;
switch (op) { case '+': a += b; break; case '-': a -= b; break; case '*': a *= b; break; case '/': if (b == 0.0) return errno = ERR_INVALID; a /= b; break; case '^': a = pow(a, b); break; default: return errno = ERR_INVALID; } } return a; }
// 进制转换 // 返回值为错误号 int convert(char *str, int source, int dest) { ULONGLONG n; ULONGLONG num = 0; // 用于存储原数 char *p; double dbl; static char *tpl = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // 字符串中允许出现的字符列表 int i, j; // 当source=dest时不能直接return, 否则无法检查出str中的无效字符
errno = 0; // 清除错误标志(errno.h中定义的全局变量) if (*str == '+' || *str == '-') str++; // 忽略正负号
/* 将原数由字符串转换为int类型 */ i = strlen(str); // str不计正负号的长度 j = 0; // j表示当前处理的是source进制下该数的哪一位 for (i--; i >= 0; i--, j++) // 遍历str的每一位数 { if (str[i] >= 'a' && str[i] <= 'z') str[i] -= 'a' - 'A'; // 将小写字母转换为大写字母 p = strchr(tpl, str[i]); // 在tpl字符串中查找str[i]字符 if (p == NULL) return ERR_INVALID; // 在tpl中未找到, 说明这个字符无效 n = p - tpl; // 当前位的数值, 例如A为10, G为16 if (n >= source) return ERR_INVALID; // 出现了原进制中不允许出现的数字
/* 如果powl函数计算出的结果太大以致于用double型无法表示, 则会将errno改为非零值 但double型也可以表示很大但不精确的数, 所以将其值转换为无符号int型后与原值进行比较 如果不相等, 就认为计算出错 */ dbl = powl(source, j); // source的j次方 if (errno || dbl != (UINT)dbl) return ERR_TOOBIG; // 如果数字太大powl无法计算 n *= (ULONGLONG)dbl; num += n; }
/* 再转换为目标进制数的字符串(从低位到高位) */ for (i = 0; num != 0; i++) { str[i] = tpl[num % dest]; num /= dest; } str[i] = '\0'; reverse(str, i); // 调换字符串方向, 变为从高位到低位 return NOERR; // 无错误 }
BOOL format(char *str, char *buf) { BOOL only_zero = TRUE; // 是否在字符串中只发现了数字0 BOOL pt_flag = FALSE; // 确保小数点只出现一次 char *pstr = buf; // 存储buf的开始位置 int i; *buf = '$'; if (*str == '+') str++; // 忽略正号 else if (*str == '-') *buf++ = *str++;
// 忽略开头的0 while (*str == '0' && str[1] != '\0' && str[1] != '.') str++;
// 求整数部分的位数 for (i = 0; str[i] != '.' && str[i] != '\0'; i++);
// 复制整数部分和小数部分的字符 // i只表示整数部分的位数 while (*str != '\0') { if ((*str >= '0' && *str <= '9') || (!pt_flag && *str == '.')) { if (*str == '.') pt_flag = TRUE; else if (*str != '0') only_zero = FALSE; // 发现了非0数
*buf++= *str++; // 复制字符
if (!pt_flag) { i--; // 剩余位数减1 if (i != 0 && i % 3 == 0) *buf++ = ','; // 添加千位分隔符 } } else return FALSE; // 出现了非法字符 } if (*(buf - 1) == '.') *(buf - 1) = '\0'; // 最后一个字符不能只是小数点 else *buf = '\0';
// 不允许出现-0或-0.0000 if (only_zero) memmove(pstr, pstr + 1, strlen(pstr));
return TRUE; }
// 输入字符串到字符数组中 // buffer为数组, capacity为数组容量 void getstr(char *buffer, int capacity) { char ch = 0; fflush(stdin); // 删除之前输入的内容 if (capacity > 1) { // 跳过开头的无效字符 do { ch = getchar(); } while (ch == ' ' || ch == '\n' || ch == '\r');
// 存储第一个字符 *buffer++ = ch; capacity--;
// 存储其他字符, 直到容量用完或者遇到回车符 while (ch = getchar(), capacity-- > 1 && ch != '\n') *buffer++ = ch; *buffer = '\0'; } else if (capacity == 1) { *buffer = '\0'; // 若容量为1, 则只能存放\0 capacity--; } if (ch != '\n') puts("输入的内容太长, 已截断部分内容"); fflush(stdin); // 跳过剩下的未存储的字符 }
// 获取下一个运算符 // 如果没有运算符则返回\0 char get_operator(char **str, int *len) { char ch; // 跳过运算符前的空格和换行 while (**str == ' ' || **str == '\n' || **str == '\r') { (*str)++; (*len)--; } ch = *(*str)++; // 获得的运算符 (*len)--; // 跳过运算符后的空格和换行 if (ch != '\0') // 如果ch为\0, 则后续内容可以不管 { while (**str == ' ' || **str == '\n' || **str == '\r') { (*str)++; (*len)--; } } return ch; }
// 反转字符串中的部分字符 void reverse(char *str, int n) { char temp; int i; int m = n / 2; for (i = 0; i < m; i++) { temp = str[i]; str[i] = str[n - i - 1]; str[n - i - 1] = temp; } }
// 判断进制是否合法 BOOL valid_radix(int n) { if (n >= 2 && n <= 36) return TRUE; else { puts("抱歉, 本计算器只能转换2~36进制的数"); return FALSE; } }
void view_logs(void) { char ch; FILE *fp = fopen(FILENAME, "r"); if (fp == NULL) { puts("无历史记录"); return; } // 输出文件中的所有字符, 直到结束 // 文件的末尾一定是换行符 while (ch = fgetc(fp), !feof(fp)) putchar(ch); fclose(fp); }
|