0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScriptで電卓を作る 1

0
Last updated at Posted at 2025-12-09

JavaScriptで電卓を作る① 🧮

(入力制御編:0 / 00 / 小数点 / 演算子)

CleanShot 2025-12-10 at 06.34.53@2x.png

こんにちは、tushiko23です 👋

今回は JavaScriptで電卓を作ります。
普段、買い物や家計簿で何気なく電卓アプリを開いていますが、「これって中でどう動いてるんだろう?」と気になって作ってみました。

特に大変だったのは 入力制御(エラー制御) の部分です。

  • + や × を連続で押してもおかしくならないようにする

  • 小数点が .. みたいに連続して入らないようにする

  • 00 が不自然に入らないようにする

など、「普通の電卓なら当たり前」 を実装するのが意外と難しくてかなり勉強になりました🔥

この記事を読めば、
✅ 仕様を文章化して整理する力
✅ 関数分割して実装する力
がつくと思うので、1つずつ見ていきましょう!

必要な機能 ✅

  • 0〜9 の数字を押すとディスプレイに反映される
  • 数字・演算子(+ - × ÷)・小数点を入力すると文字列として連結できる
  • = を押すと計算結果をディスプレイに返す
  • AC を押すとディスプレイが 0 に戻る

「ボタンを押したら反映されるだけじゃん」と思うかもですが、
入力制御を入れると一気に難しくなります 😂

実装の手順 🛠️

① 電卓画面を HTML / CSS で作成
② ボタンを押したら display に反映
③ AC を押したら 0 に戻す
④ 入力制御①(小数点 / 0 / 00 / 演算子の基礎)
⑤ 入力制御②(- の符号/演算子判定、表示×÷と内部*/の変換)
⑥ 計算処理(配列化 → * / 優先 → + - 計算)

今回は ①〜⑤の途中(入力制御中心) を解説します!

完成コード(折りたたみ)📦

index.html Calculator
<div class="buttons">
  <button class="button" onclick="inputNumber('7')">7</button>
  <button class="button" onclick="inputNumber('8')">8</button>
  <button class="button" onclick="inputNumber('9')">9</button>
  <button class="button" onclick="inputOperator('+')">+</button>
</div>

<div class="buttons">
  <button class="button" onclick="inputNumber('4')">4</button>
  <button class="button" onclick="inputNumber('5')">5</button>
  <button class="button" onclick="inputNumber('6')">6</button>
  <button class="button" onclick="inputOperator('-')">-</button>
</div>

<div class="buttons">
  <button class="button" onclick="inputNumber('1')">1</button>
  <button class="button" onclick="inputNumber('2')">2</button>
  <button class="button" onclick="inputNumber('3')">3</button>
  <button class="button" onclick="inputOperator('*')">&times;</button>
</div>

<div class="buttons">
  <button class="button" onclick="inputNumber('0')">0</button>
  <button class="button" onclick="inputNumber('00')">00</button>
  <button class="button" onclick="inputDot('.')">.</button>
  <button class="button" onclick="inputOperator('/')">&divide;</button>
</div>

<div class="buttons">
  <button class="button" onclick="reset()">AC</button>
  <button class="button" onclick="equal()">=</button>
</div>
main.css .wrapper { margin: 1.5rem; }

.display {
box-sizing: border-box;
background-color: black;
font-size: 2rem;
line-height: 2.5rem;
color: white;
text-align: right;
border: none;
padding: 5px;
width: 35%;
}

.buttons {
display: flex;
width: 35%;
}

.button {
flex: 4;
font-size: 1.5rem;
padding: .25rem 0;
margin: .2rem;
border-radius: .25rem;
}

main.js
const display = document.querySelector(".display");

// 数字の入力
function inputNumber(num) {
  const value = display.value;
  if (value === "0では除算できません") {
    if (num === "0" || num === "00") {
      display.value = "0";
      return;
    }
    display.value = num;
    return;
  }

  if (display.value === "0" && num !== "00") {
    display.value = num;
    return;
  }

  if (num == "00") {
    const expr = display.value;
    const lastNumber = expr.split(/[+\-×÷]/).pop();
    if (lastNumber === "" || display.value === "0") {
      return;
    } else {
      display.value += "00";
      return; 
    }
  }

  display.value += num;
};

// 小数点の入力
function inputDot() {
  const value = display.value;
  if (value === "0では除算できません") {
    display.value = '0';
    return;
  }

  const expr = display.value;
  const lastNumber = expr.split(/[+\-×÷]/).pop();
  if (expr === "") return;
  if (lastNumber === "" || lastNumber.includes(".")) return;

  display.value += ".";
};

// 演算子の入力
function inputOperator(op) {
  const value = display.value;
  const lastRaw = value.slice(-1);

  if (value === "0では除算できません") {
    display.value = "0";
    return;
  }

  // 画面表示のx÷を引数で受け取った時にそれを*/で返して内部側で処理
  const toInternal = (c) => {
    if (c === "×") return "*";
    if (c === "÷") return "/";
    return c;
  }
  const last = toInternal(lastRaw);  
  
  // 内部表示の*/を引数で受け取った時にそれをx÷で返して外部側で処理
  const toDisplay = (c) => {
    if (c === "*") return "×";
    if (c === "/") return "÷";
    return c;
  }

  const opForDisplay = toDisplay(op);

  if (display.value === "0" && op === "-") {
    display.value = "-";
    return;
  }

  if ("+-*/".includes(last)) {
    // 押したのが-で直前が-でない時-を符号として許可する、
    if (op === "-" && last !== "-") {
      display.value += "-";
      return;
    }
    // 演算子が連続で押された時に全部を捨てて1つにする処理
    // 6 + - x → 6x
    let trimmed = value;
    while ("+-*/".includes(toInternal(trimmed.slice(-1)))) {
      trimmed = trimmed.slice(0, -1);
    }
    display.value = trimmed + opForDisplay;
    return;
  };

  display.value += opForDisplay;
};

// -(マイナス)の処理 (符号か演算子を判定)
function mergeUnaryMinus(tokens) {
  const result = [];

  for (let i = 0; i < tokens.length; i++) {
    const t = tokens[i];
    const prev = result[result.length - 1];
    const next = tokens[i + 1];
    if (
      t === "-" &&
      next !== undefined && 
      !isNaN(Number(next)) &&
      (i === 0 || ["+", "-", "*", "/"].includes(prev))
    ) {
      result.push("-" + next);
      i++;
    } else {
      result.push(t);
    }
  }
  
  return result;
};

// 演算子*/を優先して計算する処理
function applyMulDiv(tokens) {
  const result = [];
  let i = 0;
  while (i < tokens.length) {
    const t = tokens[i];
    if (t === "*" || t === "/"){
    const prev = Number(result.pop());
    const next = Number(tokens[i + 1]);
    
    let calc;
    if (t === "*" ) {
        calc = prev * next;
      } else {
        if (next === 0) {
          display.value = "0では除算できません";
          return null;
        }
        calc = prev / next;
      };

  result.push(String(calc));
    i += 2;
    } else {
      result.push(t);
      i++;
    };

  }
  return result;
};

// =(イコール)押下時の処理
function equal () {
  const exprDisplay = display.value;
  const exprForCalc = exprDisplay.replace(/×/g, "*").replace(/÷/g, "/");
  let tokens = exprForCalc.match(/(\d+(?:\.\d+)?|[+\-*/])/g);

  if (!tokens) return; 
  if (exprDisplay === "0では除算できません") {
    display.value = "0";
    return;
  }
  if (!exprDisplay) return;

  // 末尾が演算子なら、計算せずに何もしない
  const lastChar = exprDisplay.slice(-1); // 末尾1文字だけ取り出す
  if ("+-*/×÷".includes(lastChar)) {
    return;
  }

  tokens = mergeUnaryMinus(tokens);
  tokens = applyMulDiv(tokens);
  // mergeUnaryMinusとapplyMulDiv関数実行後のtokens配列の空列を防止
  if (!tokens) return;

  let result = Number(tokens[0]);

  for (let i = 1; i < tokens.length; i += 2) {
    const op = tokens[i];
    const num = Number(tokens[i+1]);
    if (op === "+") {
      result = result + num;  
    } else if (op === "-") {
      result = result - num;
    }
  }

  // 電卓で小数点以下9~10桁を四捨五入し値の誤差を防ぐ
  result = Math.round(result * 1000000000) / 1000000000;
  display.value = result;
};

// Cボタン
function reset() {
  display.value = "0";
};

(1) 数字・小数点・演算子を表示に反映する 📥

HTMLではonclickで関数を呼び、値を渡します。

<input class="display" readonly type="text" value="0">

<button onclick="inputNumber('7')">7</button>
<button onclick="inputDot('.')">.</button>
<button onclick="inputOperator('+')">+</button>

displayreadonlyにして手入力できないようにしています

ボタンを押したときに 関数へ値を渡して表示を更新します

JavaScript側ではこう👇

const display = document.querySelector(".display");

function inputNumber(num) {
  display.value += num;
}

function inputDot() {
  display.value += ".";
}

function inputOperator(op) {
  display.value += op;
}

この時点では「押した値を連結するだけ」です。

✅ 例
1 → 212
3+23+2

(2) ACを押すと0に戻す ♻️

<button onclick="reset()">AC</button>

function reset() {
  display.value = "0";
}

これでリセットは完成です 👍

(3) ここからが本番:入力制御を入れる 🚧

このままだと、普通の電卓では起きない挙動が出ます。

  • 0 → 1 で 01 になってしまう

  • 1.. みたいに小数点が連続入力できてしまう

  • 3+00 ができてしまう

なので、「入力できていいケースだけ通す」 制御を入れていきます。

数字の入力制御(0 / 00)🔢
✅ 仕様(こうしたい)

  • 0 のとき 1〜9 を押したら 上書き(01にしない)

  • 0 のとき 00 を押しても 何もしない

  • 3+ の直後など(最後の数が空)のとき 00 を押しても 何もしない

それ以外は普通に連結

✅ 実装:0の上書き(01対策)

if (display.value === "0" && num !== "00") {
  display.value = num;
  return;
}

+= だと 0 に 1 が足されて 01 になってしまうので、
ここだけは 代入(=)で上書き しています。

✅ 実装:00 が押せるか判定する

if (num == "00") {
  const expr = display.value;
  const lastNumber = expr.split(/[+\-×÷]/).pop();
  if (lastNumber === "" || display.value === "0") {
    return;
  } else {
    display.value += "00";
    return; 
  }
}

ポイントはここ👇

split(/[+\-×÷]/) で 演算子ごとに分割

.pop()で 最後の数字だけ取り出す

それが ""(空)なら、今は 演算子直後なので 00 を禁止

✅ 例
3+ のときは分割結果が ['3', '']になり、最後が ''
00 を入力させない

小数点の入力制御(..対策)🔸

✅ 仕様

  • すでにその数に . が入っていたら 2回目は無視
  • 演算子の直後(最後の数が空)は 入力させない

✅ 実装

const expr = display.value;
const lastNumber = expr.split(/[+\-×÷]/).pop();

if (expr === "") return;
if (lastNumber === "" || lastNumber.includes(".")) return;

display.value += ".";

これで、

  • 1.2.(2個目の小数点) → 入らない

  • 3+.(演算子直後の小数点) → 入らない

が実現できます ✅

マイナス以外の演算子(×÷の表示と内部処理)✖️➗

電卓では表示上は × ÷ を出したいけど、
JavaScriptの計算処理では * / を使いたいです。

そこで変換する関数を用意します。

const toInternal = (c) => {
  if (c === "×") return "*";
  if (c === "÷") return "/";
  return c;
};

const toDisplay = (c) => {
  if (c === "*") return "×";
  if (c === "/") return "÷";
  return c;
};

表示は × ÷

内部の判定や計算は * /

という設計にしておくと、後の実装がめちゃくちゃ楽になります 💡

次回予告 🚀

次回はついに、電卓の鬼門である -(マイナス) に入ります!

  • を「符号」として入力できるケース

  • を「引き算」として扱うケース

演算子が連続したときの整理(上書き)

= を押したときの計算(配列化 → 優先順位)

このあたりを 具体例つきで丁寧に 解説します。

ここまで読んでいただきありがとうございました〜!🙌

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?