前回は時間電卓なる奇妙なものを作成しましたが、今回は普通の電卓をつくります。
前回作成した時間電卓
C#なら簡単 DateTime構造体とTimeSpan構造体を使って時間計算機をつくる
C#なら簡単 TimeSpan構造体を使って時間電卓をつくる
Contents
普通の電卓では正しい計算ができない
多くの電卓で「1 + 2 × 3」を計算すると「9」と表示されます。しかし足し算とかけ算が混合した計算の場合、かけ算が先なので正解は 「7」です。ではこのような計算をするにはどうすればよいでしょうか?
試しにWindowsに搭載されている電卓で計算すると「標準電卓」では「9」と表示されますが、「関数電卓」を使うと正しく「7」と表示されます。だから今からやろうとしているのは車輪の再発明です。それでもつきあってくれる方は以下の記事を読んでください。
文字列の数式を計算する方法
文字列の計算式を前から順番にではなく適切な順番で計算する方法としては以下のようなコードであれば正しい計算結果が格納されるのでこれを利用します。
1 2 |
int ret = 1 + 2 × 3; // この場合は 7 が代入される |
CSharpCodeProviderを使うのは非推奨
CSharpCodeProviderを使って、計算式が含まれるコードの文字列をコンパイルし実行すれば正しい計算結果が取得できます。
ただこの方法はおすすめできません。計算式が変わるたびにその都度コンパイルが必要になります。そうなればメモリに毎回アセンブリが読み込まれるので、その解放について気を遣わなければならなくなり、下手をするといつかOutOfMemoryになる危険性が大きいです。実用的かといわれると大いに疑問です。
DataTableを使う方法
DataTableを使う方法は有力です。DataTableに数式をいれて計算させてその結果を取得するという方法です。
ただし、DataTable.Compute(“計算式”, “”)が返す値をそのまま利用しようとしてもうまくいきません。まず文字列に変換してdouble.Parseメソッドでdouble型に変換された値を取得します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
using System; class Program { static System.Data.DataTable _dt = new System.Data.DataTable(); static void Main() { string s1 = "1 + 2 * 3"; string s2 = "(1 + 2) * 3"; double ret1 = Calculate1(s1); double ret2 = Calculate1(s2); Console.WriteLine($"{s1} = {ret1}"); Console.WriteLine($"{s2} = {ret2}"); return; } static double Calculate1(string str) { // return _dt.Compute(str, ""); // return (double)_dt.Compute(str, ""); // 上記はいずれも例外が発生する string s = _dt.Compute(str, "").ToString(); return double.Parse(s.ToString()); } } |
実行結果
1 + 2 * 3 = 7
(1 + 2) * 3 = 9
巨大な数を計算する
これで一見問題なさそうなのですが、巨大な数を計算しようとするとエラーがでます。
1 2 3 4 5 6 7 8 9 |
class Program { static void Main() { string s1 = "500000 * 100000000000000 / 7"; double ret1 = Calculate(s1); Console.WriteLine($"{s1} = {ret1}"); } } |
発生した例外
1 2 3 4 5 6 7 8 9 |
Unhandled exception. System.OverflowException: Value is either too large or too small for Type 'Int64'. at System.Data.BinaryNode.EvalBinaryOp(Int32 op, ExpressionNode left, ExpressionNode right, DataRow row, DataRowVersion version, Int32[] recordNos) at System.Data.BinaryNode.Eval(DataRow row, DataRowVersion version) at System.Data.BinaryNode.Eval() at System.Data.BinaryNode.Optimize() at System.Data.BinaryNode.Optimize() at System.Data.DataExpression.Bind(DataTable table) at System.Data.DataExpression..ctor(DataTable table, String expression, Type type) at System.Data.DataTable.Compute(String expression, String filter) |
このような例外が発生するのは”500000 * 100000000000000 / 7″がInt64型の範囲を超えてしまっているからです。Calculateメソッドが返す値の型はdouble型ですが、演算はInt64型同士なのでInt64型の範囲を超えてしまうとそれまでです。
そこで計算式の最初に「1.0 × 」をつけます。これだと最初からdouble型で計算がおこなわれるので例外は発生しません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Program { static void Main() { string s1 = "500000 * 100000000000000 / 7"; double ret1 = Calculate2(s1); Console.WriteLine($"{s1} = {ret1}"); } static double Calculate2(string str) { string s = _dt.Compute("1.0 *" + str, "").ToString(); return double.Parse(s.ToString()); } } |
大きな数の場合、指数表示になってしまいますが、計算結果は取得できます。
実行結果
500000 * 100000000000000 / 7 = 7.142857142857143E+18
文字列の数式を計算するWebアプリをJavaScriptでつくる
文字列の数式を計算するWebアプリをJavaScriptでつくります。サーバーサイドにおける処理はなく、全部クライアントサイドでおこないます。
まず0~9と四則演算にもちいる記号と()が書かれたボタンを用意します。そしてボタンがクリックされたら計算式を表示します。=のボタンが押されたら計算してその結果を返します。また入力をミスしたときのひとつ前にもどって修正できるように←ボタンをつけました。
HTML部分
HTMLソースを示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>文字列の数式を計算する電卓をつくる</title> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="description" content="ウェブ上で使える文字列の数式を計算する電卓です。" /> <style> .num { width: 40px; height: 30px; margin-bottom: 10px; } .num0 { width: 80px; height: 30px; } .operator { width: 45px; height: 30px; margin-bottom: 10px; } .left { float: left; } .right { float: left; margin-left: 30px; } .both { clear: both; height:5px; } #container { margin: 40px 0px 40px 40px; } #formula { margin: 40px 0px 10px 0; } </style> </head> <body> <div id="container"> <div class="left"> <input type="button" value="1" class = "num" onclick="buttonClick(1)"> <input type="button" value="2" class = "num" onclick="buttonClick(2)"> <input type="button" value="3" class = "num" onclick="buttonClick(3)"><br> <input type="button" value="4" class = "num" onclick="buttonClick(4)"> <input type="button" value="5" class = "num" onclick="buttonClick(5)"> <input type="button" value="6" class = "num" onclick="buttonClick(6)"><br> <input type="button" value="7" class = "num" onclick="buttonClick(7)"> <input type="button" value="8" class = "num" onclick="buttonClick(8)"> <input type="button" value="9" class = "num" onclick="buttonClick(9)"><br> <input type="button" value="." class = "num" onclick="buttonClick('.')"> <input type="button" value="0" class = "num0" onclick="buttonClick(0)"> </div> <div class="right"> <input type="button" value="÷" class = "operator" onclick="buttonClick('÷')"> <input type="button" value="(" class = "operator" onclick="buttonClick('(')"><br> <input type="button" value="×" class = "operator" onclick="buttonClick('×')"> <input type="button" value=")" class = "operator" onclick="buttonClick(')')"><br> <input type="button" value="-" class = "operator" onclick="buttonClick('-')"> <input type="button" value="←" class = "operator" onclick="buttonBackClick()"><br> <input type="button" value="+" class = "operator" onclick="buttonClick('+')"> <input type="button" value="AC" class = "operator" onclick="buttonAllClearClick()"> </div> <div class="both"></div> <div class="left"> <input type="button" value="=" class = "num0" onclick="buttonResultClick()"> </div> <div class="both"></div> <div id="formula"></div> <div id="result"></div> </div> <script type='text/javascript' src='./app.js'></script> </body> </html> |
JavaScript部分
数字、小数点、演算子が書かれたボタンをクリックするとbuttonClick関数が呼び出され、数式が生成されていきます。また入力された数式はリアルタイムで表示されます。
app.js
1 2 3 4 5 6 7 8 9 |
let formula = ''; // ここに数式を格納する function buttonClick(e){ formula += e; let formulaElement = document.getElementById("formula"); formulaElement.textContent = formula; formulaElement.style.fontWeight= "bold "; formulaElement.style.fontSize= "24px"; } |
←と書かれているボタンをクリックされたら計算式のうち一番右にある文字を消去します。
1 2 3 4 5 6 7 8 9 10 |
function buttonBackClick(){ let len = formula.length; if(len == 0) return; formula = formula.slice(0, len-1); let formulaElement = document.getElementById("formula"); formulaElement.textContent = formula; formulaElement.style.fontWeight= "bold "; formulaElement.style.fontSize= "24px"; } |
ACと書かれたボタンがクリックされたらこれまでに入力された数式と表示されている計算結果をクリアします。
1 2 3 4 5 |
function buttonAllClearClick(){ formula = ''; document.getElementById("formula").textContent = formula; document.getElementById("result").textContent = ''; } |
=と書かれているボタンがクリックされたら表示されている計算式を実際に計算できるように置き換えます。全角文字の+-×÷を+-*/に置き換えます。そしてFunction(‘return (‘+calc_string+’);’)()を実行すると計算結果が取得できるので、これを表示します。
もし変数 formula に計算式として正しくない文字列が格納されている場合は例外が発生します。このときは数式が不正である旨を表示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function buttonResultClick(){ let resultElement = document.getElementById("result"); resultElement.style.fontWeight= "bold "; resultElement.style.fontSize= "24px"; try{ let calc_string = formula.replace(/+/g, "+"); calc_string = calc_string.replace(/-/g, "-"); calc_string = calc_string.replace(/×/g, "*"); calc_string = calc_string.replace(/÷/g, "/"); var result = Function('return ('+calc_string+');')(); resultElement.textContent = result; } catch(e){ resultElement.textContent = "数式が不正"; } } |
30時間ぐらい時間の節約になりました。ありがとうございます。