一派掌门 二十级 |
#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); }
|