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