JavaScript電卓作りで全部エラー!私が3日かけて学んだ計算処理の罠

javascript icon
JavaScript

「電卓くらい簡単に作れるでしょ」

そう思っていた時期が私にもありました。

こんにちは、とまだです。

初めてJavaScriptで電卓を作ろうとした時、私は完全に甘く見ていました。

結果?

3日間エラーと格闘する羽目になったんです。

  • 数字ボタンを押しても画面に何も出ない
  • 計算ボタンを押したら「NaN」の文字が...
  • 「0.1 + 0.2」を計算したら「0.30000000000000004」

「え?なんで?」の連続でした。

でも今なら分かります。電卓作りは、JavaScriptの基本が全部詰まった最高の教材だったんです。

今回は現役のエンジニア、そして元プログラミングスクール講師としての経験から、JavaScript電卓の作り方について解説します。

なぜ電卓作りでみんな失敗するのか

簡単に言うと、電卓作りには見た目以上の難しさが潜んでいるからです。

私が最初に作った電卓、こんな感じでした:

// 私の黒歴史コード
function calculate() {
  let num1 = document.getElementById("num1").value;
  let num2 = document.getElementById("num2").value;
  let result = num1 + num2;
  alert(result);  // 「12」じゃなくて「1」「2」って表示される...
}

「1 + 2 = 12」

...は?

これ、文字列連結になってたんです。JavaScriptあるあるですよね。

電卓作りの3大ハマりポイント

1. 文字列と数値の罠

// ❌ よくある失敗
"1" + "2"  // "12" (文字列の連結)

// ✅ 正しい方法
Number("1") + Number("2")  // 3
// または
parseFloat("1") + parseFloat("2")  // 3

私はこれに気づくまで1時間悩みました...。

2. 小数点の恐怖

// JavaScriptの有名なバグ?いいえ、仕様です
0.1 + 0.2  // 0.30000000000000004

// 対策:小数点以下を丸める
Math.round((0.1 + 0.2) * 10) / 10  // 0.3

お客さんに「100円 + 200円 = 300.00000000000006円です」なんて表示したら、信用失いますよね。

3. ゼロ除算の悪夢

// ❌ エラーチェックなし
function divide(a, b) {
  return a / b;  // b が 0 だと Infinity
}

// ✅ エラーチェックあり
function divide(a, b) {
  if (b === 0) {
    return "エラー:0で割ることはできません";
  }
  return a / b;
}

実際に動く!シンプル電卓を作ってみよう

では、私の失敗を踏まえた「動く電卓」を作ってみましょう。

Step1: HTML(見た目)

<!DOCTYPE html>
<html>
<head>
  <title>JavaScript電卓</title>
  <style>
    .calculator {
      width: 300px;
      margin: 50px auto;
      padding: 20px;
      background: #f0f0f0;
      border-radius: 10px;
      box-shadow: 0 2px 10px rgba(0,0,0,0.1);
    }
    
    .display {
      background: white;
      padding: 10px;
      margin-bottom: 10px;
      text-align: right;
      font-size: 24px;
      border-radius: 5px;
      min-height: 40px;
    }
    
    .buttons {
      display: grid;
      grid-template-columns: repeat(4, 1fr);
      gap: 10px;
    }
    
    button {
      padding: 20px;
      font-size: 18px;
      border: none;
      border-radius: 5px;
      cursor: pointer;
      background: white;
    }
    
    button:hover {
      background: #e0e0e0;
    }
    
    .operator {
      background: #ff9800;
      color: white;
    }
    
    .operator:hover {
      background: #f57c00;
    }
    
    .equals {
      background: #4caf50;
      color: white;
      grid-column: span 2;
    }
    
    .clear {
      background: #f44336;
      color: white;
    }
  </style>
</head>
<body>
  <div class="calculator">
    <div class="display" id="display">0</div>
    <div class="buttons">
      <button class="clear" onclick="clearDisplay()">C</button>
      <button onclick="appendNumber('(')">(</button>
      <button onclick="appendNumber(')')">)</button>
      <button class="operator" onclick="appendOperator('/')">÷</button>
      
      <button onclick="appendNumber('7')">7</button>
      <button onclick="appendNumber('8')">8</button>
      <button onclick="appendNumber('9')">9</button>
      <button class="operator" onclick="appendOperator('*')">×</button>
      
      <button onclick="appendNumber('4')">4</button>
      <button onclick="appendNumber('5')">5</button>
      <button onclick="appendNumber('6')">6</button>
      <button class="operator" onclick="appendOperator('-')"></button>
      
      <button onclick="appendNumber('1')">1</button>
      <button onclick="appendNumber('2')">2</button>
      <button onclick="appendNumber('3')">3</button>
      <button class="operator" onclick="appendOperator('+')">+</button>
      
      <button onclick="appendNumber('0')">0</button>
      <button onclick="appendNumber('.')">.</button>
      <button class="equals" onclick="calculate()">=</button>
    </div>
  </div>
  
  <script src="calculator.js"></script>
</body>
</html>

Step2: JavaScript(動き)

// calculator.js
let display = document.getElementById('display');
let currentInput = '0';
let shouldResetDisplay = false;

// 画面をクリア
function clearDisplay() {
  currentInput = '0';
  display.textContent = '0';
}

// 数字を追加
function appendNumber(num) {
  // 新しい計算を始める場合
  if (shouldResetDisplay) {
    currentInput = '';
    shouldResetDisplay = false;
  }
  
  // 最初の0を消す
  if (currentInput === '0' && num !== '.') {
    currentInput = '';
  }
  
  // 小数点の重複チェック
  if (num === '.' && currentInput.includes('.')) {
    return;
  }
  
  currentInput += num;
  display.textContent = currentInput;
}

// 演算子を追加
function appendOperator(op) {
  // 連続した演算子を防ぐ
  const lastChar = currentInput[currentInput.length - 1];
  if (['+', '-', '*', '/'].includes(lastChar)) {
    currentInput = currentInput.slice(0, -1);
  }
  
  currentInput += op;
  display.textContent = currentInput;
  shouldResetDisplay = false;
}

// 計算実行
function calculate() {
  try {
    // evalは危険だけど、電卓なら大丈夫
    // 本番環境では使わないでね!
    let result = eval(currentInput);
    
    // 小数点の誤差対策
    if (typeof result === 'number') {
      result = Math.round(result * 100000000) / 100000000;
    }
    
    display.textContent = result;
    currentInput = result.toString();
    shouldResetDisplay = true;
    
  } catch (error) {
    display.textContent = 'エラー';
    currentInput = '0';
    shouldResetDisplay = true;
  }
}

実践的な電卓の活用例

1. ECサイトの料金計算

// 商品価格 × 個数 × 税率
function calculateTotal(price, quantity, taxRate = 1.1) {
  const subtotal = price * quantity;
  const total = subtotal * taxRate;
  
  // 円単位で丸める
  return Math.floor(total);
}

// 使用例
const itemPrice = 980;
const quantity = 3;
const total = calculateTotal(itemPrice, quantity);
console.log(`合計: ${total}`);  // 合計: 3234円

2. ローン計算機

// 月々の返済額を計算
function calculateMonthlyPayment(principal, annualRate, years) {
  const monthlyRate = annualRate / 100 / 12;
  const numberOfPayments = years * 12;
  
  if (monthlyRate === 0) {
    return principal / numberOfPayments;
  }
  
  const payment = principal * 
    (monthlyRate * Math.pow(1 + monthlyRate, numberOfPayments)) / 
    (Math.pow(1 + monthlyRate, numberOfPayments) - 1);
  
  return Math.round(payment);
}

// 1000万円を年利2%で35年ローン
const monthly = calculateMonthlyPayment(10000000, 2, 35);
console.log(`月々の返済額: ${monthly.toLocaleString()}`);

3. 割り勘計算機

function splitBill(total, people, includesTax = true) {
  let amount = total;
  
  // 税込みでない場合は税を加算
  if (!includesTax) {
    amount = amount * 1.1;
  }
  
  // 一人当たりの金額(10円単位で切り上げ)
  const perPerson = Math.ceil(amount / people / 10) * 10;
  
  return {
    perPerson: perPerson,
    total: perPerson * people,
    extra: perPerson * people - amount
  };
}

// 5人で12,345円を割り勘
const result = splitBill(12345, 5);
console.log(`一人${result.perPerson}円(${result.extra}円のお釣り)`);

プロが教える実装のコツ

1. 状態管理をシンプルに

// 電卓の状態を管理するオブジェクト
const calculator = {
  displayValue: '0',
  firstOperand: null,
  waitingForOperand: false,
  operator: null
};

// 状態をリセット
function reset() {
  calculator.displayValue = '0';
  calculator.firstOperand = null;
  calculator.waitingForOperand = false;
  calculator.operator = null;
}

2. 演算を関数化

const operations = {
  '+': (a, b) => a + b,
  '-': (a, b) => a - b,
  '*': (a, b) => a * b,
  '/': (a, b) => b !== 0 ? a / b : 'Error',
  '=': (a, b) => b
};

function performOperation(operator) {
  const inputValue = parseFloat(calculator.displayValue);
  
  if (calculator.firstOperand === null) {
    calculator.firstOperand = inputValue;
  } else if (calculator.operator) {
    const result = operations[calculator.operator](
      calculator.firstOperand, 
      inputValue
    );
    
    calculator.displayValue = String(result);
    calculator.firstOperand = result;
  }
  
  calculator.waitingForOperand = true;
  calculator.operator = operator;
}

3. キーボード対応

document.addEventListener('keydown', (event) => {
  const key = event.key;
  
  if (key >= '0' && key <= '9') {
    appendNumber(key);
  } else if (key === '.') {
    appendNumber('.');
  } else if (key === '+' || key === '-' || key === '*' || key === '/') {
    appendOperator(key);
  } else if (key === 'Enter' || key === '=') {
    calculate();
  } else if (key === 'Escape' || key === 'c' || key === 'C') {
    clearDisplay();
  } else if (key === 'Backspace') {
    deleteLastChar();
  }
});

よくある質問と解決策

Q: なぜ0.1 + 0.2が0.3にならないの?

A: これはJavaScriptの仕様です。解決策:

// 方法1: 小数点以下の桁数を指定
function roundToDecimal(num, decimals) {
  return Math.round(num * Math.pow(10, decimals)) / Math.pow(10, decimals);
}

// 方法2: toFixedを使う(文字列になるので注意)
(0.1 + 0.2).toFixed(2)  // "0.30"

// 方法3: 整数にしてから計算
(10 + 20) / 100  // 0.3

Q: evalは危険って聞いたけど?

A: その通りです!本番環境では使わないでください。代替案:

// 安全な計算機の実装例
function safeCalculate(expression) {
  // 許可する文字だけを通す
  const sanitized = expression.replace(/[^0-9+\-*/().\s]/g, '');
  
  // トークンに分解して解析
  const tokens = sanitized.match(/\d+\.?\d*|[+\-*/()]/g);
  
  // ここで構文解析して計算
  // (実装は複雑なので省略)
}

Q: もっと高機能な電卓を作りたい

A: 段階的に機能を追加しましょう:

  1. メモリ機能(M+, M-, MR, MC)
  2. 関数電卓(sin, cos, log, √)
  3. 履歴機能(計算履歴の保存)
  4. グラフ表示(Chart.jsなどを使用)

まとめ

JavaScript電卓作りは、最初は「簡単そう」に見えて、実は奥が深いプロジェクトです。

私も3日間悩みましたが、その分学んだことも多かったです:

  1. 文字列と数値の変換は必須
  2. 小数点の誤差に注意
  3. エラー処理を忘れずに
  4. 状態管理をシンプルに

電卓作りで学んだことは、他のプロジェクトでも必ず役立ちます。

フォームの入力値計算、ECサイトの料金表示、データの集計処理...どこでも使える知識です。

最初は動かなくても大丈夫。私だって「1 + 2 = 12」から始まったんですから。

大切なのは、エラーを恐れずに手を動かすこと。

さあ、あなたも電卓作りに挑戦してみませんか?

きっと「なるほど!」の連続が待っていますよ。

共有:

著者について

とまだ

とまだ

フルスタックエンジニア

Learning Next の創設者。Ruby on Rails と React を中心に、プログラミング教育に情熱を注いでいます。初心者が楽しく学べる環境作りを目指しています。

著者の詳細を見る →