0

Working on a Calculator function. Everything works as it should with one exception. Any single operation or string of operations with a negative number results in NaN instead of the correct answer.

HTML

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Calculator II</title>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js" defer></script>
  </head>
  <body>
    <div class="display"><output id="output">Display</output></div>
    <div id="buttons">
      <div id="row1">
        <button class="oper" id="+">+</button><button class="oper" id="-">-</button><button class="oper" id="*">*</button>
      </div>
      <div id="row2">
        <button class="oper" id="/">/</button><button class="alt" id="back"><-</button><button class="alt" id="clear">C</button>
      </div>
      <div id="row3">
        <button class="nbr" id="1">1</button><button class="nbr" id="2">2</button><button class="nbr" id="3">3</button>
      </div>
      <div id="row4">
        <button class="nbr" id="4">4</button><button class="nbr" id="5">5</button><button class="nbr" id="6">6</button>
      </div>
      <div id="row5">
        <button class="nbr" id="7">7</button><button class="nbr" id="8">8</button><button class="nbr" id="9">9</button>
      </div>
      <div id="row6">
        <button class="nbr" id="0">0</button><button id="decimal">.</button><button class="alt" id="equals">=</button>
      </div>
    </div>
  </body>
</html>

JS

let display = "0";
let result = 0;
let num1 = "";
let operand = "";
let num2 = "";
let outputElement = document.getElementById("output");

//basic math functions
function add(num1, num2) {
  result = display = num1 + num2; // combining result and display to a single line.
  console.log("add", result, "d", display);
  return result;
}

function subtract(num1, num2) {
  result = display = num1 - num2;
  console.log("sub", result, "d", display);
  return result;
}

function multiply(num1, num2) {
  result = display = num1 * num2;
  console.log("mult", result, "d", display);
  return result;
}

function divide(num1, num2) {
  result =
    num2 === 0
      ? (console.log("divide by zero? Hoser!"),
        alert("cannot divide by zero"),
        0)
      : (display = num1 / num2);
}

function clear() {
  result = 0;
  display = "0";
  num1 = "";
  operand = "";
  num2 = "";
  console.log("clear", num1, operand, num2, "d", display);
}

function performCalculation(){
  switch (operand) {
    case "+":
      console.log(operand);
      add(parseFloat(num1), parseFloat(num2));
      break;
    case "-":
      console.log(operand);
      subtract(parseFloat(num1), parseFloat(num2));
      break;
    case "*":
      console.log(operand);
      multiply(parseFloat(num1), parseFloat(num2));
      break;
    case "/":
      console.log(operand);
      divide(parseFloat(num1), parseFloat(num2));
      break;
    default:
      console.log("switch", "Houston we have a problem!");
  }
}

function equals() {
  console.log("equals");
  const calculationString = display.replace(/[^0-9+\-*/.]/g, "");
  console.log('cs', calculationString);
  const calculations = calculationString.split(/([-+*/])/);
  console.log('c', calculations);

  result = parseFloat(calculations[0]);
  display = result.toString();

  for (let i = 1; i < calculations.length; i += 2) {
    operand = calculations[i];
    num1 = display;
    num2 = calculations[i + 1];

    performCalculation();

    display = result.toString();
  }
outputElement.textContext = display;
}

let buttons = document.getElementById("buttons");
buttons.addEventListener("click", clickHandler);

function clickHandler(e) {
  if (display === "0") {
    display = "";
  }
  let clicked = e.target;
  if (clicked.classList.contains("nbr")) {
    let number = clicked.id;
    // if (display === "0") {
    //   display = "";
    // }
    if (operand === "") {
      if (!num1.includes(".") || num1.split(".")[1].length < 3) {
        display += number;
        num1 += number;
      }
    } else {
      if (!num2.includes(".") || num2.split(".")[1].length < 3) {
        display += number;
        num2 += number;
      }
    }
  } else if (clicked.classList.contains("oper")) {
    display += clicked.id;
    if (clicked.id === "-" && operand === "") {
      if (num1 === "") {
        num1 += "-";
      } else {
        num2 += "-";
      }
    } else {
      operand = clicked.id;
            console.log("CH Oper", operand);
    }
  } else if (clicked.id === "equals") {
    console.log("=");
    equals();
    display = parseFloat(display).toFixed(3).toString();
    display = display.replace(/\.?0*$/, "");
  } else if (clicked.id === "clear") {
    clear();
  } else if (clicked.id === "back") {
    display = display.slice(0, -1);
    if (operand === "") {
      num1 = num1.slice(0, -1);
    } else {
      num2 = num2.slice(0, -1);
    }
  } else if (clicked.id === "decimal") {
    display += ".";
    if (operand === "") {
      if (!num1.includes(".")) {

        num1 += ".";
      }
    } else {
      if (!num2.includes(".")) {
        num2 += ".";
      }
    }
  }

  console.log("clicked", clicked, "d", display);
  console.log(num1, operand, num2, "d", display);
  outputElement.textContent = display;
}

I believe the problem is with 'calculation' reading the array that is separating in nums and operands. I have tried a variety of tweaks to calculationString. The following was tried from the bottom up:

const calculationString = display.replace(/[^0-9+\-*/.]/g, "");
const calculations = calculationString.split(/([+\-*/])/).filter(Boolean);
const calculations = calculationString.split(/([-+*/])/).filter(Boolean);
const calculations = calculationString.split(/(?<!\d)-|\+|\*|\//);
const calculations = calculationString.split(/([-+*/])\s*/);
const calculations = calculationString.split(/(-?[-+*/])/);

I believe those are called regular expressions and I really do not understand them very well.

This is the last hurdle for me to wrap up this project short of CSS beautification. Help is much appreciated.

Loptr
  • 71
  • 5
  • Could it be that the negative sign is taken into the calculation? It could explain why you are getting Not A Number. – tomerpacific Jun 23 '23 at 19:51
  • I think the problem is this statement `const calculations = calculationString.split(/([-+*/])/)` you'll have to check if the `-` character is the first one or is after another operation symbol and don't split on it if that is the case. – Titus Jun 23 '23 at 19:51

2 Answers2

1

It's a complicated process to calculate an operation based on a string input, you can check some of the answers from here. Now a quick fix for your calculator is separating * and / first then doing + and -.

(Note: this doesn't work for bigger numbers since I'm doing the calculation.length > 2, there's a lot of checks to do there if it starts with a "-" and doesn't have another addition or subtraction afterwards)

  const firstCalculations = calculationString.split(/([*/])/);
  const calculations = firstCalculations.map((calculation) => {
    if (calculation.length > 2) {
      const secondCalculations = calculation.split(/([+-])/);
      return secondCalculations;    
    }
    return calculation;
  });

Then add a fix for the first number just in case

  if (calculations[0] === "") {
    result = 0;
  } else {
    result = parseFloat(calculations[0]);
  }

You can expand your calculator here:

let display = "0";
let result = 0;
let num1 = "";
let operand = "";
let num2 = "";
let outputElement = document.getElementById("output");

//basic math functions
function add(num1, num2) {
  result = display = num1 + num2; // combining result and display to a single line.
  return result;
}

function subtract(num1, num2) {
  result = display = num1 - num2;
  return result;
}

function multiply(num1, num2) {
  result = display = num1 * num2;
  return result;
}

function divide(num1, num2) {
  result =
    num2 === 0
      ? (console.log("divide by zero? Hoser!"),
        alert("cannot divide by zero"),
        0)
      : (display = num1 / num2);
}

function clear() {
  result = 0;
  display = "0";
  num1 = "";
  operand = "";
  num2 = "";
}

function performCalculation(){
  switch (operand) {
    case "+":
      add(parseFloat(num1), parseFloat(num2));
      break;
    case "-":
      if (num1 === "") num1 = 0;
      subtract(parseFloat(num1), parseFloat(num2));
      break;
    case "*":
      multiply(parseFloat(num1), parseFloat(num2));
      break;
    case "/":
      divide(parseFloat(num1), parseFloat(num2));
      break;
    default:
      console.log("switch", "Houston we have a problem!");
  }
}

function equals() {
  const calculationString = display.replace(/[^0-9+\-*/.]/g, "");
  const firstCalculations = calculationString.split(/([*/])/);
  const calculations = firstCalculations.map((calculation) => {
    if (calculation.length > 2) {
      const secondCalculations = calculation.split(/([+-])/);
      return secondCalculations;    
    }
    return calculation;
  });


  if (calculations[0]==="") {
    result = 0;
  }else {
    result = parseFloat(calculations[0]);
  }
  display = result.toString();

  for (let i = 1; i < calculations.length; i += 2) {
    operand = calculations[i];
    num1 = display;
    num2 = calculations[i + 1];

    performCalculation();

    display = result.toString();
  }
outputElement.textContext = display;
}

let buttons = document.getElementById("buttons");
buttons.addEventListener("click", clickHandler);

function clickHandler(e) {
  if (display === "0") {
    display = "";
  }
  let clicked = e.target;
  if (clicked.classList.contains("nbr")) {
    let number = clicked.id;
    // if (display === "0") {
    //   display = "";
    // }
    if (operand === "") {
      if (!num1.includes(".") || num1.split(".")[1].length < 3) {
        display += number;
        num1 += number;
      }
    } else {
      if (!num2.includes(".") || num2.split(".")[1].length < 3) {
        display += number;
        num2 += number;
      }
    }
  } else if (clicked.classList.contains("oper")) {
    display += clicked.id;
    if (clicked.id === "-" && operand === "") {
      if (num1 === "") {
        num1 += "-";
      } else {
        num2 += "-";
      }
    } else {
      operand = clicked.id;
    }
  } else if (clicked.id === "equals") {
    console.log("=");
    equals();
    display = parseFloat(display).toFixed(3).toString();
    display = display.replace(/\.?0*$/, "");
  } else if (clicked.id === "clear") {
    clear();
  } else if (clicked.id === "back") {
    display = display.slice(0, -1);
    if (operand === "") {
      num1 = num1.slice(0, -1);
    } else {
      num2 = num2.slice(0, -1);
    }
  } else if (clicked.id === "decimal") {
    display += ".";
    if (operand === "") {
      if (!num1.includes(".")) {

        num1 += ".";
      }
    } else {
      if (!num2.includes(".")) {
        num2 += ".";
      }
    }
  }

  outputElement.textContent = display;
}
<div class="display"><output id="output">Display</output></div>
    <div id="buttons">
      <div id="row1">
        <button class="oper" id="+">+</button><button class="oper" id="-">-</button><button class="oper" id="*">*</button>
      </div>
      <div id="row2">
        <button class="oper" id="/">/</button><button class="alt" id="back"><-</button><button class="alt" id="clear">C</button>
      </div>
      <div id="row3">
        <button class="nbr" id="1">1</button><button class="nbr" id="2">2</button><button class="nbr" id="3">3</button>
      </div>
      <div id="row4">
        <button class="nbr" id="4">4</button><button class="nbr" id="5">5</button><button class="nbr" id="6">6</button>
      </div>
      <div id="row5">
        <button class="nbr" id="7">7</button><button class="nbr" id="8">8</button><button class="nbr" id="9">9</button>
      </div>
      <div id="row6">
        <button class="nbr" id="0">0</button><button id="decimal">.</button><button class="alt" id="equals">=</button>
      </div>
    </div>
Pietro Nadalini
  • 1,722
  • 3
  • 13
  • 32
1

The reason is that you are trying to do a calculation with an empty string. My fix makes it so that if the value in the array is an empty string it is replaced with 0

In function equals() I made this change

Example: -5-10

let calculations = calculationString.split(/([-+*/])/);
// [ '', "-", "5", "-", "10" ]

calculations = calculations.map(x => x === '' ? 0 : x);
// [ 0, "-", "5", "-", "10" ]

let display = "0";
let result = 0;
let num1 = "";
let operand = "";
let num2 = "";
let outputElement = document.getElementById("output");

//basic math functions
function add(num1, num2) {
    result = display = num1 + num2; // combining result and display to a single line.
    console.log("add", result, "d", display);
    return result;
}

function subtract(num1, num2) {
    result = display = num1 - num2;
    console.log("sub", result, "d", display);
    return result;
}

function multiply(num1, num2) {
    result = display = num1 * num2;
    console.log("mult", result, "d", display);
    return result;
}

function divide(num1, num2) {
    result =
        num2 === 0
            ? (console.log("divide by zero? Hoser!"),
                alert("cannot divide by zero"),
                0)
            : (display = num1 / num2);
}

function clear() {
    result = 0;
    display = "0";
    num1 = "";
    operand = "";
    num2 = "";
    console.log("clear", num1, operand, num2, "d", display);
}

function performCalculation() {
    switch (operand) {
        case "+":
            console.log(operand);
            add(parseFloat(num1), parseFloat(num2));
            break;
        case "-":
            console.log(operand);
            subtract(parseFloat(num1), parseFloat(num2));
            break;
        case "*":
            console.log(operand);
            multiply(parseFloat(num1), parseFloat(num2));
            break;
        case "/":
            console.log(operand);
            divide(parseFloat(num1), parseFloat(num2));
            break;
        default:
            console.log("switch", "Houston we have a problem!");
    }
}

function equals() {
    console.log("equals");
    const calculationString = display.replace(/[^0-9+\-*/.]/g, "");
    console.log('cs', calculationString);

    // START FIX /////////////////////////////////////////
    let calculations = calculationString.split(/([-+*/])/);

    calculations = calculations.map(x => x === '' ? 0 : x);
    console.log('c', calculations);
    // END FIX ///////////////////////////////////////////

    result = parseFloat(calculations[0]);
    display = result.toString();

    for (let i = 1; i < calculations.length; i += 2) {
        operand = calculations[i];
        num1 = display;
        num2 = calculations[i + 1];

        performCalculation();

        display = result.toString();
    }
    outputElement.textContext = display;
}

let buttons = document.getElementById("buttons");
buttons.addEventListener("click", clickHandler);

function clickHandler(e) {
    if (display === "0") {
        display = "";
    }
    let clicked = e.target;
    if (clicked.classList.contains("nbr")) {
        let number = clicked.id;
        // if (display === "0") {
        //   display = "";
        // }
        if (operand === "") {
            if (!num1.includes(".") || num1.split(".")[1].length < 3) {
                display += number;
                num1 += number;
            }
        } else {
            if (!num2.includes(".") || num2.split(".")[1].length < 3) {
                display += number;
                num2 += number;
            }
        }
    } else if (clicked.classList.contains("oper")) {
        display += clicked.id;
        if (clicked.id === "-" && operand === "") {
            if (num1 === "") {
                num1 += "-";
            } else {
                num2 += "-";
            }
        } else {
            operand = clicked.id;
            console.log("CH Oper", operand);
        }
    } else if (clicked.id === "equals") {
        console.log("=");
        equals();
        display = parseFloat(display).toFixed(3).toString();
        display = display.replace(/\.?0*$/, "");
    } else if (clicked.id === "clear") {
        clear();
    } else if (clicked.id === "back") {
        display = display.slice(0, -1);
        if (operand === "") {
            num1 = num1.slice(0, -1);
        } else {
            num2 = num2.slice(0, -1);
        }
    } else if (clicked.id === "decimal") {
        display += ".";
        if (operand === "") {
            if (!num1.includes(".")) {

                num1 += ".";
            }
        } else {
            if (!num2.includes(".")) {
                num2 += ".";
            }
        }
    }

    console.log("clicked", clicked, "d", display);
    console.log(num1, operand, num2, "d", display);
    outputElement.textContent = display;
}
<div class="display"><output id="output">Display</output></div>
<div id="buttons">
    <div id="row1">
        <button class="oper" id="+">+</button><button class="oper" id="-">-</button><button class="oper"
            id="*">*</button>
    </div>
    <div id="row2">
        <button class="oper" id="/">/</button><button class="alt" id="back"><-< /button><button class="alt"
                    id="clear">C</button>
    </div>
    <div id="row3">
        <button class="nbr" id="1">1</button><button class="nbr" id="2">2</button><button class="nbr"
            id="3">3</button>
    </div>
    <div id="row4">
        <button class="nbr" id="4">4</button><button class="nbr" id="5">5</button><button class="nbr"
            id="6">6</button>
    </div>
    <div id="row5">
        <button class="nbr" id="7">7</button><button class="nbr" id="8">8</button><button class="nbr"
            id="9">9</button>
    </div>
    <div id="row6">
        <button class="nbr" id="0">0</button><button id="decimal">.</button><button class="alt"
            id="equals">=</button>
    </div>
</div>
54ka
  • 3,501
  • 2
  • 9
  • 24
  • 1
    This works and it's very streamlined. Now I get to chew on why this works. Your format of showing the edit and example separately and 'highlighted' in the code was very helpful. – Loptr Jun 23 '23 at 20:35