// src/features/calculatorSlice.ts import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { evaluate } from "mathjs"; export type BaseType = "DEC" | "HEX" | "BIN" | "OCT"; interface CalculatorState { expression: string; result: string; shiftMode: boolean; ans: string; base: BaseType; } const initialState: CalculatorState = { expression: "", result: "0", shiftMode: false, ans: "0", base: "DEC", }; const calculatorSlice = createSlice({ name: "calculator", initialState, reducers: { appendInput: (state, action: PayloadAction) => { state.expression += action.payload; state.shiftMode = false; }, clear: (state) => { state.expression = ""; state.result = "0"; state.shiftMode = false; }, deleteLast: (state) => { state.expression = state.expression.slice(0, -1); }, toggleShift: (state) => { state.shiftMode = !state.shiftMode; }, changeBase: (state, action: PayloadAction) => { const newBase = action.payload; if (state.base === newBase) return; try { // Parse the current result string back to a decimal integer safely let decValue = 0; if (state.base === "HEX") decValue = parseInt(state.result, 16); else if (state.base === "BIN") decValue = parseInt(state.result, 2); else if (state.base === "OCT") decValue = parseInt(state.result, 8); else decValue = parseInt(state.result, 10); if (isNaN(decValue)) decValue = 0; // Convert the decimal integer to the newly requested base let newStr = ""; if (newBase === "DEC") newStr = decValue.toString(10); else if (newBase === "HEX") newStr = (decValue >>> 0).toString(16).toUpperCase(); else if (newBase === "BIN") newStr = (decValue >>> 0).toString(2); else if (newBase === "OCT") newStr = (decValue >>> 0).toString(8); state.base = newBase; state.result = newStr; state.expression = newStr; // Override expression so user can compute from here } catch (e) { state.result = "Error"; } }, calculateResult: (state) => { try { if (!state.expression) return; let parsedExpression = state.expression.replace( /Ans/g, `(${state.ans})`, ); // Prefix Base-N numbers so mathjs understands them (e.g., A becomes 0xA) if (state.base === "HEX") { parsedExpression = parsedExpression.replace( /\b[0-9A-F]+\b/g, (m) => `0x${m}`, ); } else if (state.base === "BIN") { parsedExpression = parsedExpression.replace( /\b[01]+\b/g, (m) => `0b${m}`, ); } else if (state.base === "OCT") { parsedExpression = parsedExpression.replace( /\b[0-7]+\b/g, (m) => `0o${m}`, ); } const evaluated = evaluate(parsedExpression); let formattedResult = ""; if (state.base !== "DEC") { // Bitwise and Base operations only apply to integers const intVal = Math.trunc(Number(evaluated)); // (>>> 0) safely converts negative numbers into 32-bit unsigned Two's Complement if (state.base === "HEX") formattedResult = (intVal >>> 0).toString(16).toUpperCase(); else if (state.base === "BIN") formattedResult = (intVal >>> 0).toString(2); else if (state.base === "OCT") formattedResult = (intVal >>> 0).toString(8); } else { formattedResult = Number.isInteger(evaluated) ? String(evaluated) : Number(evaluated) .toFixed(8) .replace(/\.?0+$/, ""); } state.result = formattedResult; state.expression = formattedResult; state.ans = formattedResult; state.shiftMode = false; } catch (error) { state.result = "Syntax ERROR"; } }, }, }); export const { appendInput, clear, deleteLast, toggleShift, changeBase, calculateResult, } = calculatorSlice.actions; export default calculatorSlice.reducer;