From 1d6cf81b8e44350a25005c23fcbc2b160316a537 Mon Sep 17 00:00:00 2001 From: "brindha.venkatesan" Date: Thu, 23 Jul 2020 21:03:08 +0530 Subject: [PATCH] Issue fix for Bug #16 - Support for operator precedence --- src/logic/calculate.js | 248 +++++++++++++++++++++-------------------- 1 file changed, 128 insertions(+), 120 deletions(-) diff --git a/src/logic/calculate.js b/src/logic/calculate.js index 97e653205..2aee00fe9 100644 --- a/src/logic/calculate.js +++ b/src/logic/calculate.js @@ -2,133 +2,141 @@ import Big from "big.js"; import operate from "./operate"; import isNumber from "./isNumber"; - /** - * Given a button name and a calculator data object, return an updated - * calculator data object. - * - * Calculator data object contains: - * total:String the running total - * next:String the next number to be operated on with the total - * operation:String +, -, etc. - */ -export default function calculate(obj, buttonName) { - if (buttonName === "AC") { - return { - total: null, - next: null, - operation: null, - }; - } - - if (isNumber(buttonName)) { - if (buttonName === "0" && obj.next === "0") { - return {}; - } - // If there is an operation, update next - if (obj.operation) { - if (obj.next) { - return { next: obj.next + buttonName }; - } - return { next: buttonName }; - } - // If there is no operation, update next and clear the value - if (obj.next) { - const next = obj.next === "0" ? buttonName : obj.next + buttonName; - return { - next, - total: null, - }; - } - return { - next: buttonName, - total: null, - }; - } + Calculator data object contains: + numStack - Numbers that are yet to be processed are pushed to stack. + operStack - Operators that are yet to be processed are pushed to stack. + lastNumPressed - Last number pressed by the user. + lastOperPressed - Last operaator pressed by the user. + lastPressed : "number" | "operator" - Whether last pressed + displayValue: The value displayed in the display area. +*/ +export default function calculate(obj, key) { + let numStack = obj.numStack || []; + let operStack = obj.operStack || []; + let lastNumPressed = obj.lastNumPressed; + let lastOperPressed = obj.lastOperPressed; + let lastPressed = obj.lastPressed; + let displayValue = obj.displayValue; - if (buttonName === "%") { - if (obj.operation && obj.next) { - const result = operate(obj.total, obj.next, obj.operation); - return { - total: Big(result) - .div(Big("100")) - .toString(), - next: null, - operation: null, - }; - } - if (obj.next) { - return { - next: Big(obj.next) - .div(Big("100")) - .toString(), - }; - } - return {}; + let processSingleOperation = ()=>{ + let secondNum = numStack.pop(); + let firstNum = numStack.pop(); + let result = operate(firstNum, secondNum, operStack.pop()); + numStack.push(parseFloat(result)); + displayValue = result; } - - if (buttonName === ".") { - if (obj.next) { - // ignore a . if the next number already has one - if (obj.next.includes(".")) { - return {}; + + let processStack = (processAll)=>{ + if(numStack.length>1){ + if(lastPressed === "operator"){ + operStack.pop(); + } + if(processAll){ + while(numStack.length>1){ + processSingleOperation(); + } + }else{ + processSingleOperation(); } - return { next: obj.next + "." }; - } - return { next: "0." }; - } - - if (buttonName === "=") { - if (obj.next && obj.operation) { - return { - total: operate(obj.total, obj.next, obj.operation), - next: null, - operation: null, - }; - } else { - // '=' with no operation, nothing to do - return {}; } + return displayValue; } - - if (buttonName === "+/-") { - if (obj.next) { - return { next: (-1 * parseFloat(obj.next)).toString() }; - } - if (obj.total) { - return { total: (-1 * parseFloat(obj.total)).toString() }; + let validCalcKeys = ["AC", "+/-", "%", "÷", "7", "8", "9", "x", "4", "5", "6", "-", "1", "2", "3", "+","0", ".", "=", "Backspace"]; + if(validCalcKeys.includes(key)){ + if(key === "AC"){ + numStack = []; + operStack = []; + lastNumPressed = null; + lastOperPressed = null; + lastPressed = null; + displayValue = null; + lastPressed = key; + }else if(key === "="){ + processStack(true); + lastPressed = key; + }else if(key === "+/-"){ + lastNumPressed = parseFloat(displayValue) * -1; + displayValue = String(lastNumPressed); + numStack.pop(); + numStack.push(lastNumPressed); + lastPressed = key; + }else if(key === "Backspace"){ + /** Last character is removed when backspace is pressed */ + displayValue = displayValue.slice(0, displayValue.length-1); + lastNumPressed = parseFloat(displayValue || 0); + numStack.pop(); + numStack.push(lastNumPressed); + lastPressed = "number"; + }else{ + let isDecimalPt = key === "."; + if(isNumber(key) && (!isDecimalPt || !displayValue.includes("."))){ + if(lastPressed === "number"){ + displayValue = (displayValue === "0" && !isDecimalPt ? "" : displayValue) + key; + /* Since we are concating to the previous number, we need to replace the last pushed value */ + numStack.pop(); + }else{ + /* On pressing (= (or) +/- (or) AC), the previous numbers are omitted. + */ + if(lastPressed === "=" || lastPressed === "+/-"){ + numStack=[]; + } + displayValue = isDecimalPt ? "0." : key; + } + lastNumPressed = parseFloat(displayValue || 0); + numStack.push(lastNumPressed) + lastPressed = "number"; + }else{ + if(key === '%'){ + if(displayValue && displayValue !== "0"){ + if(lastPressed === 'operator'){ + operStack.pop(); + } + let updatedNum = Big(displayValue).div(Big("100")); + numStack.pop(); + numStack.push(updatedNum); + displayValue = String(updatedNum); + lastPressed = "number"; + } + }else{ + if(lastPressed){ + if(lastPressed === "operator"){ + /** + * If lastpressed key is an operator, it has to be replaced. + */ + operStack.pop(); + }else{ + /** + * Behavior implemented from Mac calculator. + * 1. If last pressed operator precedence is equalto / morethan the current operators hierachy, the previous operations are calculated before proceeding with the current one. + * 2. (x|÷|%) has high precedence than (+|-) + */ + let getPrecedence = (operator)=> ["x", "÷"].includes(operator); + let currentOperPrecedence = getPrecedence(key); + let prevOperPrecedence = getPrecedence(lastOperPressed); + if ((currentOperPrecedence === prevOperPrecedence) || (currentOperPrecedence