なぜ足し算より掛け算を先に計算するのか?

1+2×3
  • 突然ではあるが、上記数式の答えは、7である。(2×3=6、1+6=7)
  • 左から順に計算すると9になるのだが、9と答えてはいけない。(1+2=3、3×3=9)
  • 四則演算(しそくえんざん)には、加減算よりも乗除算を先に計算する、というルールがあるのだ。
  • ルールと言われると、決まり事だから守らなければならない、と思って今まで何の疑問も抱かず計算してきた。

ところで一体、どうしてそんなルールになったのだろう?

ルールだから?

  • 自分が少年の頃はルールだから、規則だから、と言われると渋々納得していた。
  • 周りの皆もそうするし、乗除算を先に計算しないとテストでも○が貰えないので、言われたとおりに計算してきた。
  • そんなことを繰り返すうちに、それはいつしか当然の常識となってしまい、何の疑問も感じなくなっていた。
  • では、自分が大人の今、少年から同じ質問をされたら「ルールなんだよ」と答えるべきなのか?
  • いや、その一言で済ませてしまうのは、あまりにも怠惰である。
  • ルールとしか言えないのは自分が知らないことの言い訳なのだ。
  • ルールとは、皆でそうしようと決めて守る約束事である。
  • 皆でそうしようと決めたことには、どこかに合理的な理由があってそうしたはず。
  • その合理的な理由とは何なのか、それを探って、教えてあげるべきなのだ。

掛算の定義から考える

(いずれも 0 でない)自然数 m と n に対して、m を n 個分加えた数


m × n, m · n, mn
などのように書いて m に n を掛けた数とか m と n の積、m 掛ける n などという。

乗法 - Wikipedia

つまり...

0 = 2×0
2 = 2×1
2+2 = 2×2
2+2+2 = 2×3
2+2+2+2 = 2×4
...

なのだ。

  • 掛け算とは、足し算によって定義される概念なのだ。
  • 足し算と掛け算では、概念の次元が違っている。
  • 計算する時には同じ次元の概念にしておくべき。
1+2×3 = 1+2+2+2
  • 同じ次元の概念なら、先に計算しようと、後に計算しようと、答えは常に7となる。
  • ところで毎回、掛け算を足し算の概念に展開して、計算するのは面倒である。
  • だから、掛け算九九を覚えて、足し算の概念に展開した後の計算結果のみ想像して、数式全体を計算しているのだ。
  • 掛け算を先に計算するというよりは、より基本的な概念である足し算に展開していたのだ。
  • 足し算に展開する過程で、より合理的に計算するために、掛け算九九を利用して、その計算結果のみ利用していた。

掛け算九九が存在しない世界を想像してみる

  • 大人になると、掛け算九九のような数表を無意識のうちに利用して、掛け算を計算している気になっている。
  • しかし、その計算の実体は記憶から導いているだけで、純粋な計算とは言えないのである。
  • ドラえもんの秘密道具に、かの有名な「もしもボックス」という電話ボックスがある。
  • もしも、掛け算九九が存在しない世界だったら、掛け算を計算するためには、足し算に展開して、地道に足し算するしかないのである。
  • 掛け算の混じった数式は、より基本的な概念の足し算に統一することで、はじめて計算できるようになるのだ。

日常的な合理性から考える

以上のように考えたとしても、ルールとしては、左から順番に計算するというルールにしても悪くない気がする。

1+2×3 = 9
  • 上記のようなルールとならなかったのは、なぜなのか?
  • 日常生活の中で計算する状況を考えてみる。
  • すると、圧倒的に掛け算してから足し算する状況が多いことに気付いた。
    • レジでの代金精算は、商品単価×数量を足し算していくことの繰り返し。
    • お金の計算も、100円が3枚、10円が2枚...のように確認しながら、暗黙の掛け算をしながら、足し算している。
    • 回転寿しの代金計算も、お皿の種類ごとに枚数を掛け算した結果を足し算している。
  • 掛け算した結果を合計するという手順は、日常に即したルールだったのだ。
  • もちろん、足し算した結果に掛け算するという状況もないわけではない。
    • 回転寿しに3人で行って、本日のお勧め5品を3皿ずつ握ってもらう場合など。
  • 5品の金額がそれぞれ100円、200円、300円、400円、500円だったとして...
    • 一人の代金を求めて、3人分する方法なら、簡潔に計算できる。
(100+200+300+400+500)×3 = 4500円
    • 一方、お皿ごとに掛け算すると、掛け算の回数が5回に増えてしまい、煩雑である。
100×3+200×3+300×3+400×3+500×3 = 4500円
  • しかし、寿司屋に3人で行って、まったく同じものを食べて、帰ってくるという状況は極めて稀である。
  • 一般的には、3人がそれぞれ自分の好みの物を、好きなだけ食べる、という状況がありふれているだろう。
  • つまり、1の状況よりも、2の状況の方がありふれているのだ。
    1. 100+200+300+400+500×3 = (100+200+300+400+500)×3
    2. 100×3+200×3+300×3+400×3+500×3 = (100×3)+(200×3)+(300×3)+(400×3)+(500×3)
  • ならば、よくある状況である掛け算を先に計算して、括弧を省略可能なルールとした方が、合理的なのだ。

因数分解と展開

  • 1は数式を因数分解した形、2は数式を展開した形。
    1. 100+200+300+400+500×3 = (100+200+300+400+500)×3
    2. 100×3+200×3+300×3+400×3+500×3 = (100×3)+(200×3)+(300×3)+(400×3)+(500×3)
  • 因数分解は、状況の中に共通因数を見つけられなければ数式にできないが、(特別な状況)
  • 展開は、どのような状況であっても必ず数式にできるのだ。(必ず可能)
  • 日常に、掛け算した結果を足し算する状況が多いのも頷ける。(どのような状況でも使える計算方法だから)

必ず括弧で明示するルールにならなかったのは?

暗黙の優先順位なんて作らずに、明示的に括弧で括れば良いという考え方もある。

  • ところで、数式は、可能な限りシンプルに表現できることが好まれる。
  • 小学時代は四則演算で十分だが、中学、高校と数学の奥深くに進むほどに、新たな概念が登場する。
  • ベキ乗、ルート、対数...など、様々な演算子が入り乱れて、複雑な数式に出会う機会もある。
  • その時、演算子の優先順位が左から順に計算するルールのみだと、複雑な数式の中に多数の括弧が含まれて、さらに複雑な様相となってしまう可能性がある。
  • シンプルな表現を目指して、プラス・マイナス・代数の概念を学んだ中学以降は、四則演算の記号は姿を消してしまう。
    • 加減算については、数値のプラスかマイナスかを明示して並べるだけになる。(減算はマイナスの加算という概念になる)
    • 掛け算については、代数の掛け算記号は省略される。(割り算は逆数の掛け算に変換される)
  • そのような暗黙のルールがあるからこそ、かの有名な公式も美しいと言われるのだ。
E=mc^2
      • ^は、ベキ乗の意味。
  • 仮に、左から順に計算するルールのみだと、とんでもない誤解の公式になってしまう。
    • ベキ乗は、掛け算より優先順位が高いのだ。
  • mc^2 = m(c^2)である。(くれぐれも(mc)^2ではないので誤解のないように)
  • 暗黙の優先順位は、数式をシンプルで美しくするのである。シンプルで美しい数式は、分かりやすい。
  • よって、加減算より乗除算を優先することで余分な括弧を省略するルールは、より多くの状況で数式をシンプルにする、理に適ったルールなのである。

優先順位を考慮して計算するアルゴリズム

  • 今どきのスクリプト言語は、当然のように1+2*3=7と計算してくれる。(AppleScriptでもそうなる)
  • ところが、数式をテキストとして受け取り、構文解析して、優先順位を考慮した計算を自力で処理するのは、かなり手強い。
  • そのような必要性はほとんどないのだが、手法の一つとしてちゃんと理解しておきたかったので、試しにやってみた。
1+2
=+12 ...... ポーランド記法
=12+ ...... 逆ポーランド記法
  • 逆ポーランド記法の素晴らしいところは、一般的な数式を、括弧不要で優先順位を考慮した数式に変換できるところである。
1+2*3 → 123*+ → 「2と3を掛け算して、その結果と1を足し算する」
2*3+4*5 → 23*45*+ → 「2と3を掛け算して、4と5を掛け算して、その結果同士を足し算する」
1+2*3+4 → 123*+4+ → 「2と3を掛け算して、その結果と1を足し算して、その結果と4を足し算する」
  • 数字の順番は変わらず、演算子の順番だけ変化している。
  • そして、一旦逆ポーランド記法に変換してしまえば、とても簡単に目的の計算が達成できてしまうのだ。
  • その方法は、演算子を見つけたら手前の2要素を演算子に従って計算する、という非常にシンプルなもの。
  • 難関は、一般的な数式を逆ポーランド記法に変換する処理の部分。やってみた。(AppleScript
  • 実験的なサンプルコードなので、数値は1桁、使える記号は+-*/()のみ、数式のエラーチェックもなし。


global re_poland
global temp

--calc(revrse_polish_notation("5+8*(4+5)"))
--calc(revrse_polish_notation("(5+8)*(4+5)"))
--calc(revrse_polish_notation("1+2*3+4*5"))
calc(revrse_polish_notation("1+(2*3+4)*5"))
on calc(formula_elements) set temp to {} repeat with e in formula_elements
if e is in "01234456789" then
push_temp(e) else
set num1 to pop_temp() set num2 to pop_temp() push_temp(run script (num2 & e & num1)) end if
end repeat
{formula_elements, temp} end calc

on revrse_polish_notation(formula) set re_poland to {} set temp to {""} repeat with e in (formula's text items) if e is in "01234456789" then
push_re_poland(e) else if e is in "(" then
push_temp(e) else if e is in ")" then
bracket_repeat() else
operator_repeat(e) end if
end repeat
operator_repeat(")") re_poland
end revrse_polish_notation



on bracket_repeat() repeat
set last_op to pop_temp() if last_op = "(" then
exit repeat
else
push_re_poland(last_op) end if
end repeat
end bracket_repeat

on operator_repeat(op) repeat
set last_op to pop_temp() if priority(last_op) < priority(op) then
push_temp({last_op, op}) exit repeat
else
push_re_poland(last_op) end if
end repeat
end operator_repeat



on push_re_poland(elements) repeat with e in (elements as list) set re_poland's end to e as text
end repeat
end push_re_poland

on push_temp(elements) repeat with e in (elements as list) set temp's end to e as text
end repeat
end push_temp

on pop_temp() set i to temp's item -1
set temp to temp's reverse's rest's reverse
i
end pop_temp

on priority(i) if i is in "()" then return 0
if i is in "+-" then return 1
if i is in "*/" then return 2
-1
end priority

  • AppleScriptには、もともとpush・popの概念がないから、まずはその処理をするハンドラ(メソッド)から作らなければならなかった。
  • 逆ポーランド記法にして計算する方法は定石とされる方法だけど、アセンブラC言語のスタック処理と素晴らしく相性の良い方法だと思った。
  • 今どきのスクリプト言語なら、その高度な機能を利用して、より効率的な処理方法があるのかもしれない。