此篇是繼續上篇的內容的補充,上篇博客只完整了基本面向對象的框架搭建等步驟,而最重要的核心算法部分卻未講解,此篇博客將詳細的講解如何實現win10基本計算器的後臺算法。
由於篇幅有限,這裏我只講解算法,完整源碼可以點擊右邊github鏈接獲取:win10計算器代碼(源碼裏還有詳細的功能介紹文檔)
試玩鏈接:win10計算器
注意:由於此項目運用到了es6的面向對象語法,所以不能通過html頁面直接運行,這裏給不會運行的朋友介紹一個操作很簡單的運行方式。
首先,用vscode裝一個名字爲Live Server的插件。
裝載完成後回到HTML頁面,點擊右下角的Go Live按鈕。
只需兩步,輕鬆運行此類項目。
此項目效果這裏我就不演示了,你們自己點擊鏈接運行玩玩。
前言鋪墊以及介紹完畢,下面開始進入正題。
首先在這裏我是通過12個全局變量來定義各種運行時狀態,雖然變量過多顯得有些複雜,但是實際效果的確很好。接下來先看看這12個全局變量的含義:
上面的截圖來源自源碼裏的文檔,那裏面還有更多詳細功能實現介紹,這裏我只是講解這12個變量。
當前大屏幕數據:記錄大號字體屏幕的當前顯示數據。
當前小屏幕數據:記錄小號字體屏幕的當前顯示數據。
最後一個四則運算符號:記錄小屏幕表達式裏最右邊的那個四則運算符號。
上一次操作類型:這是最重要的一個狀態記錄變量,記錄上一步的操作類型。
這裏總共只分成了四種操作類型:數字操作,單點運算操作(一元運算操作),四則運算操作以及等號操作。
上一次大屏幕更新數據:也就是記錄最後一個四則運算符前面的表達式在大屏幕所顯示的數值。
小屏幕最後一個數據:這個應該很好理解。
=前的操作數:這個也很好理解。
下面五個全局bool變量,前四個應該不難理解,我只說明最後一個變量的含義。
出現錯誤後的重置標誌:它是用來記錄是否觸發了異常的操作顯示。
這裏只有三種異常操作顯示:負數開根號,除數爲0以及數值溢出。
最後,我再補充一點,我在處理數值溢出時爲了簡便,所以設定數值位數超出屏幕即爲數值溢出,這其實與真正意義上的數值溢出並不一樣。
算法代碼:
/* eslint-disable max-len */
import DisplayData from '../views/displayData.js';
import screenTexts from './screenTextSign.js';
import { removeButtonsEvent, recoverButtonsEvent } from '../buttons/dealButton.js';
let bigScreenData = '0';
let smallScreenData = '';
let lastArithmeticSymbol = '';
let previousOperationType = '';
let previousBigScreenData = '0';// Record the result of the expression before the last four operations
let smallLastScreenData = '';
let equalPreviousData = '';
let isHaveDecimalPoint = false;
let isHaveNegative = false;
let isClearEqual = true;
let isHaveArithmetic = false;
let isRecover = false;
const displayData = new DisplayData();
const previousOperationTypes = {
singleOperation: 'SingleOperation',
number: 'Number',
equal: 'Equal',
arithmetic: 'Arithmetic',
};
export const getBigScreenStr = () => bigScreenData;
export const getSmallScreenStr = () => smallScreenData;
function resetAll() {
bigScreenData = '0';
smallScreenData = '';
lastArithmeticSymbol = '';
previousOperationType = previousOperationTypes.number;
previousBigScreenData = '0';
smallLastScreenData = '';
equalPreviousData = '';
isHaveDecimalPoint = false;
isHaveNegative = false;
isClearEqual = true;
isHaveArithmetic = false;
}
function recover() {
recoverButtonsEvent();
resetAll();
}
function ceHandler() {
bigScreenData = '0';
if (!isClearEqual) {
resetAll();
} else if (isHaveArithmetic && previousOperationType === previousOperationTypes.singleOperation) {
smallScreenData = smallScreenData.substring(0, smallScreenData.length - smallLastScreenData.length);
smallLastScreenData = '';
isHaveDecimalPoint = false;
isHaveNegative = false;
}
}
function delHandler() {
if (!isClearEqual) {
smallScreenData = '';
} else if (previousOperationType === previousOperationTypes.number) {
const dataLength = isHaveNegative ? bigScreenData.length - 1 : bigScreenData.length;
if (dataLength === 1) {
bigScreenData = '0';
isHaveNegative = false;
} else {
if (bigScreenData[bigScreenData.length - 1] === '.') {
isHaveDecimalPoint = false;
}
bigScreenData = bigScreenData.substring(0, bigScreenData.length - 1);
}
}
}
function numberSignHandler() {
// Change small screen data
if (previousOperationType === previousOperationTypes.number) {
if (bigScreenData === '0') {
return;
}
previousOperationType = previousOperationTypes.number;
} else if (previousOperationType === previousOperationTypes.equal) {
delHandler();
smallLastScreenData = `negate(${bigScreenData})`;
smallScreenData += smallLastScreenData;
previousOperationType = previousOperationTypes.singleOperation;
} else if (previousOperationType === previousOperationTypes.arithmetic) {
smallLastScreenData = `negate(${bigScreenData})`;
smallScreenData += smallLastScreenData;
previousOperationType = previousOperationTypes.singleOperation;
} else {
smallScreenData = smallScreenData.substring(0, smallScreenData.length - smallLastScreenData.length);
smallLastScreenData = `negate(${smallLastScreenData})`;
smallScreenData += smallLastScreenData;
previousOperationType = previousOperationTypes.singleOperation;
}
// Change big screen data
if (isHaveNegative) {
isHaveNegative = false;
bigScreenData = bigScreenData.substring(1);
} else {
bigScreenData = `-${bigScreenData}`;
isHaveNegative = true;
}
}
function decimalPointHandler() {
if (previousOperationType === previousOperationTypes.number) {
if (isHaveDecimalPoint) {
return;
}
bigScreenData += '.';
isHaveDecimalPoint = true;
return;
}
if (!isClearEqual || (isHaveArithmetic && previousOperationType === previousOperationTypes.singleOperation)) {
ceHandler();
}
bigScreenData = '0.';
isHaveDecimalPoint = true;
}
function numberHandler(text) {
if (previousOperationType === previousOperationTypes.number) {
bigScreenData = bigScreenData === '0' ? text : bigScreenData + text;
} else if (!isClearEqual) {
delHandler();
bigScreenData = text;
previousBigScreenData = bigScreenData;
} else if (isHaveArithmetic && previousOperationType === previousOperationTypes.singleOperation) {
ceHandler();
bigScreenData = text;
} else {
bigScreenData = text;
}
}
function getPointLength(number) {
return number.length - number.indexOf('.') - 1;
}
function checkResultNumber(result) {
if (!result.includes('.')) {
return result;
}
let index = result.length - 1;
while (result[index] === '0') {
index--;
}
return result.substring(0, index + 1);
}
function arithmetic(numOne, sign, numTwo) {
let countNumOne = 0;
let countNumTwo = 0;
let result = 0;
if (numOne.includes('.')) {
countNumOne = getPointLength(numOne);
}
if (numTwo.includes('.')) {
countNumTwo = getPointLength(numTwo);
}
const index = Math.max(countNumOne, countNumTwo);
switch (sign) {
case screenTexts.addition:
result = (Number(numOne) + Number(numTwo)).toFixed(index);
return checkResultNumber(result);
case screenTexts.subtraction:
result = (Number(numOne) - Number(numTwo)).toFixed(index);
return checkResultNumber(result);
case screenTexts.multiplication:
result = Number((Number(numOne) * Number(numTwo)).toFixed(countNumOne + countNumTwo));
return Number.isInteger(Number(result)) ? result.toString() : result.toFixed(Math.min(countNumOne + countNumTwo, 10));
case screenTexts.division:
if (numTwo === '0' || numTwo === '') {
throw new Error('Cannot divide by zero');
}
result = Number(numOne) / Number(numTwo);
return Number.isInteger(result) ? result.toString() : result.toFixed(Math.min(getPointLength(result.toString()), 10));
default:
return null;
}
}
function calculate() {
equalPreviousData = bigScreenData;
smallLastScreenData = bigScreenData;
smallScreenData = `${smallScreenData + bigScreenData}=`;
bigScreenData = arithmetic(previousBigScreenData, lastArithmeticSymbol, bigScreenData);
previousBigScreenData = bigScreenData;
isHaveNegative = Number(bigScreenData) < 0;
}
function checkBigData() {
if (!bigScreenData.includes('.')) {
return;
}
let index = bigScreenData.length - 1;
while (bigScreenData[index] === '0') {
index--;
}
if (bigScreenData[index] === '.') {
index--;
}
bigScreenData = bigScreenData.substring(0, index + 1);
}
function equalHandler() {
checkBigData();
if (!isClearEqual) {
if (isHaveArithmetic) {
smallScreenData = `${bigScreenData + lastArithmeticSymbol + equalPreviousData}=`;
bigScreenData = arithmetic(bigScreenData, lastArithmeticSymbol, equalPreviousData);
previousBigScreenData = bigScreenData;
isHaveNegative = Number(bigScreenData) < 0;
} else {
smallScreenData = `${bigScreenData}=`;
previousBigScreenData = bigScreenData;
smallLastScreenData = bigScreenData;
}
} else if (previousOperationType === previousOperationTypes.number) {
if (isHaveArithmetic) {
calculate();
} else {
smallScreenData = `${bigScreenData}=`;
previousBigScreenData = bigScreenData;
smallLastScreenData = bigScreenData;
}
} else if (previousOperationType === previousOperationTypes.arithmetic) {
calculate();
} else if (previousOperationType === previousOperationTypes.singleOperation) {
equalPreviousData = bigScreenData;
smallScreenData = `${smallScreenData}=`;
bigScreenData = arithmetic(previousBigScreenData, lastArithmeticSymbol, bigScreenData);
previousBigScreenData = bigScreenData;
isHaveNegative = Number(bigScreenData) < 0;
}
}
function ArithmeticHandler(text) {
checkBigData();
if (!isClearEqual) {
delHandler();
smallScreenData = bigScreenData + text;
isHaveArithmetic = true;
} else if (previousOperationType === previousOperationTypes.number) {
if (isHaveArithmetic) {
smallLastScreenData = bigScreenData;
smallScreenData = smallScreenData + bigScreenData + text;
bigScreenData = arithmetic(previousBigScreenData, lastArithmeticSymbol, bigScreenData);
isHaveNegative = Number(bigScreenData) < 0;
} else {
smallScreenData = bigScreenData + text;
smallLastScreenData = bigScreenData;
isHaveArithmetic = true;
}
previousBigScreenData = bigScreenData;
} else if (previousOperationType === previousOperationTypes.arithmetic) {
smallScreenData = smallScreenData.substring(0, smallScreenData.length - 1) + text;
} else if (previousOperationType === previousOperationTypes.singleOperation) {
smallScreenData += text;
if (isHaveArithmetic) {
bigScreenData = arithmetic(previousBigScreenData, lastArithmeticSymbol, bigScreenData);
isHaveNegative = Number(bigScreenData) < 0;
} else {
isHaveArithmetic = true;
}
previousBigScreenData = bigScreenData;
}
}
function reciprocalChange(type) {
if (type === 'equal') {
smallScreenData = `1/(${bigScreenData})`;
} else if (type === 'single') {
smallLastScreenData = `1/(${smallLastScreenData})`;
} else if (type === 'other') {
smallLastScreenData = `1/(${bigScreenData})`;
}
}
function squareChange(type) {
if (type === 'equal') {
smallScreenData = `sqr(${bigScreenData})`;
} else if (type === 'single') {
smallLastScreenData = `sqr(${smallLastScreenData})`;
} else if (type === 'other') {
smallLastScreenData = `sqr(${bigScreenData})`;
}
}
function rootSquareChange(type) {
if (type === 'equal') {
smallScreenData = `rSqr(${bigScreenData})`;
} else if (type === 'single') {
smallLastScreenData = `rSqr(${smallLastScreenData})`;
} else if (type === 'other') {
smallLastScreenData = `rSqr(${bigScreenData})`;
}
}
function singleOperation(text, type) {
let result = 0;
switch (text) {
case screenTexts.reciprocal:
reciprocalChange(type);
bigScreenData = arithmetic('1', screenTexts.division, bigScreenData);
break;
case screenTexts.square:
squareChange(type);
bigScreenData = arithmetic(bigScreenData, screenTexts.multiplication, bigScreenData);
break;
case screenTexts.rootSquare:
rootSquareChange(type);
if (bigScreenData[0] === '-') {
throw new Error('Invalid input');
}
result = Number(bigScreenData) ** 0.5;
bigScreenData = Number.isInteger(result) ? result.toString() : result.toFixed(Math.min(getPointLength(result.toString()), 10));
break;
default:
break;
}
}
function singleOperationHandler(text) {
if (previousOperationType === previousOperationTypes.equal) {
singleOperation(text, 'equal');
smallLastScreenData = smallScreenData;
} else if (previousOperationType === previousOperationTypes.singleOperation) {
smallScreenData = smallScreenData.substring(0, smallScreenData.length - smallLastScreenData.length);
singleOperation(text, 'single');
smallScreenData += smallLastScreenData;
} else {
singleOperation(text, 'other');
smallScreenData += smallLastScreenData;
}
isHaveNegative = Number(bigScreenData) < 0;
}
function percentSign() {
if (lastArithmeticSymbol === '+' || lastArithmeticSymbol === '-') {
bigScreenData = (Number(previousBigScreenData) * Number(bigScreenData) * 0.01).toString();
} else {
bigScreenData = (Number(bigScreenData) * 0.01).toString();
}
}
function percentSignHandler() {
if (!isHaveArithmetic) {
resetAll();
smallScreenData = '0';
smallLastScreenData = '0';
} else if (!isClearEqual) {
percentSign();
smallScreenData = smallScreenData.substring(0, smallScreenData.length - smallLastScreenData.length);
smallLastScreenData = bigScreenData;
smallScreenData = bigScreenData;
} else if (previousOperationType === previousOperationTypes.singleOperation) {
percentSign();
smallScreenData = smallScreenData.substring(0, smallScreenData.length - smallLastScreenData.length);
smallLastScreenData = bigScreenData;
smallScreenData += bigScreenData;
} else {
percentSign();
smallLastScreenData = bigScreenData;
smallScreenData += bigScreenData;
}
}
function checkNumberOverFlow() {
let index = 0;
while (index < bigScreenData.length && (bigScreenData[index] === '0' || bigScreenData[index] === '.' || bigScreenData[index] === '-')) {
index++;
}
if (index === bigScreenData.length - 1 && (index === 11 || index === 12)) {
return true;
}
if (index === bigScreenData.length && index > 3) {
return true;
}
if (bigScreenData.length >= 13) {
return true;
}
return false;
}
function overflowHandler() {
removeButtonsEvent();
bigScreenData = 'Overflow';
isRecover = true;
}
function divisionZeroHandler() {
removeButtonsEvent();
bigScreenData = '除數不能爲0';
isRecover = true;
}
function invalidHandler() {
removeButtonsEvent();
bigScreenData = 'Invalid input';
isRecover = true;
}
function display() {
displayData.showBigScreen(bigScreenData);
displayData.showSmallScreen(smallScreenData);
}
export function dealOperation(text) {
if (isRecover) {
recover();
isRecover = false;
}
if (text === screenTexts.clearAllOperation) {
resetAll();
} else if (text === screenTexts.clearCurrentOperation) {
ceHandler();
previousOperationType = previousOperationTypes.number;
} else if (text === screenTexts.deleteNumber) {
delHandler();
} else if (text === screenTexts.addition || text === screenTexts.subtraction || text === screenTexts.multiplication || text === screenTexts.division) {
try {
ArithmeticHandler(text);
} catch (e) {
divisionZeroHandler();
display();
return;
}
lastArithmeticSymbol = text;
isClearEqual = true;
isHaveDecimalPoint = false;
previousOperationType = previousOperationTypes.arithmetic;
} else if (text === screenTexts.percentSign) {
percentSignHandler();
previousOperationType = previousOperationTypes.singleOperation;
} else if (text === screenTexts.rootSquare || text === screenTexts.square || text === screenTexts.reciprocal) {
try {
singleOperationHandler(text);
} catch (e) {
if (e.message === 'Invalid input') {
invalidHandler();
} else if (e.message === 'Cannot divide by zero') {
divisionZeroHandler();
}
display();
return;
}
previousOperationType = previousOperationTypes.singleOperation;
}
if (checkNumberOverFlow()) {
overflowHandler();
}
display();
}
export function dealNumber(text) {
if (isRecover) {
recover();
isRecover = false;
}
if (bigScreenData.length === 11 && previousOperationType === previousOperationTypes.number) {
return;
}
if (text === screenTexts.sign) {
numberSignHandler();
} else if (text === screenTexts.decimalPoint) {
decimalPointHandler();
previousOperationType = previousOperationTypes.number;
} else {
numberHandler(text);
previousOperationType = previousOperationTypes.number;
}
display();
}
export function dealEqual() {
if (isRecover) {
recover();
isRecover = false;
display();
return;
}
try {
equalHandler();
} catch (e) {
divisionZeroHandler();
display();
return;
}
isClearEqual = false;
previousOperationType = previousOperationTypes.equal;
if (checkNumberOverFlow()) {
overflowHandler();
}
display();
}
此代碼註釋很少,那是因爲我是參考以及寫好的算法思路文檔而進行編寫的,所以我建議大家可以參照源碼裏面的文檔來看源碼。
計算器部分就此告一段落,下一個task我準備把前面的Excel也改成面向對象的代碼風格,再會!