#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);
}