Javascriptで電卓を作ってみた。ソース付き。

こんにちは。ヤマヤタケシです。
プログラマになるにはどうすればいいですか?でプログラマになる第一歩は、
電卓を作るべし!
わからないなら写経すべし!
言語はとりあえずJavascriptがいい!
と、言いたい放題やりました。

しかし!
よくよく考えてみると、俺自身電卓を作った事がなかったのです!
プログラムはゲームをベーマガ(雑誌)で写経して覚えたので・・・・。

しかし、読者にやったこともない事を勧めるのは無責任です。
やってみました。Javascript で電卓を作りました!
車輪の再発明でございます。

約5時間かかりました・・・。
2時間ぐらいはスタイルタグをいじっていました。
見た目の調整がクソめんどいですね。
height=100%の指定がどうにもうまく行かなかったのです。
結局、「高さが決まらないタグが親に含まれていると、autoになる。」ことが原因でした。
スタイルシートまで勉強しちゃいました!

さて、これが作った電卓です。
ソースはこの記事の最後にあります。

小数点に誤差が出ます。
これは、2進数をベースにしたコンピュータの宿命なので、そのまま残しました。
なんとかするときは、整数ベースでやるとか、Math.roundするとか、いろいろあるんでしょうが、学習用です。
そのままにしました。

ソースは index.html にまとめました。
普通は3ファイルに分割します。
style.cssとdentaku.jsとindex.html の3つです。
今回は写経してもらうことを想定したので1つです。

プログラムは、M-VCを意識しています。
ModelとView-Contrtolで分離しています。
Modelは論理、ロジック、コア、仕組み、概念です。
Viewは見た目です。
Controlは操作です。
このように分けると仕様変更に強くなります。
Model が独立しているとUIに依存しないテストができます。
単体の自動テストなどを取り入れる場合にとても役立ちます。
View-ControlはプラットフォームのAPIに依存しますが、Model は 純粋な Javascript のみになります。
そのため、移植するときにModelはそのままいけます。

ソースについて少し説明します。
createDentakuModel()で返すインスタンスが電卓の Model です。
createView()が返すのがView-Controlです。

Javascript は高級な言語なので、C言語にはない機能がたくさんありますね。
まあ、言語間の機能の比較などは1つの言語がある程度学習が進んでからで良いと思います。
最近は、いろんな言語を時と場合によって使い分けるようになっています。
この例を作るだけなのに、html, css, javascriptの3つの言語を行き来しました。

さて、ソースをどーんと貼ります。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>電卓js</title>

<style>
.top {
    width : 100%;
    height : 100%;
    border-color: red;
    border: 1px;
}

.row {
    width : 100%;
    height : 14%;
    background-color: #888;
}

.cell {
    width : 25%;
    background-color: #fff;
}

.text {
    width : 100%;
    height : 100%;
    text-align: right;
    font-size: 1.4em;
    padding:0px;
    margin:0px;
    border:none;
}

.button {
    width : 100%;
    height : 100%;
    font-size: 1.4em;
    padding: 10px;
}

html, body {
    height : 100%;
    margin: 0px;
    padding: 0px;
}
</style>
<script>
    
function createDentakuModel()
{
    var memory = 0
    var current = 0;
    var isInput = false;
    var point = 0;
    var m = {}

    m.toString = function()
    {
	var str = ""
	str += "isInput " + isInput + " ";
	str += "current " + current + " ";
	str += "memory " + memory + " ";
	return str;
    }
    m.all_clear = function()
    {
	memory = 0
	current = 0;
	calc = null;
	isInput = false;
	point = 0;
    }

    m.point = function()
    {
	if( point == 0 ){
	    point = 1;
	}
    }

    m.number = function( num )
    {
	if( isInput == false ){
	    isInput = true;
	    current = 0;
	}

	if( 0 <= current )
        {
            //plus
	    if( 0 < point ){
	        current += num * Math.pow( 10, -point );
	        point++;
	    }
	    else {
	        current = current * 10;
	        current += num;
	    }
        }
        else
        {
            //minus
	    if( 0 < point ){
	        current -= num * Math.pow( 10, -point );
	        point++;
            }
            else
            {
	        current = current * 10;
	        current -= num;
            }
        }
    }

    m.equal = function()
    {
	if( m.calc != null ){
	    m.calc();
	}
	m.calc = null;
	memory = current;
	isInput = false;
	point = 0;
    }
    
    m.add = function()
    {
	m.equal();
	m.calc = function()
	{
	    current = memory + current;
	}
    }

    m.minus = function()
    {
	current = -current;
    }

    m.sub = function()
    {
	m.equal();
	m.calc = function()
	{
	    current = memory - current; 
	}
    }
    
    m.multi = function()
    {
	m.equal();
	m.calc = function()
	{
	    current = memory * current; 
	}
    }

    m.getDisp = function()
    { 
	return current;
    }

    m.clear = function()
    {
	current = 0;
	point = 0;
    }

    m.divide = function()
    {
	m.equal();
	m.calc = function()
	{ 
	    if( current == 0 ){
		current = "err div 0";
		return;
	    }
	    current = memory / current;
	}
    }
    return m;
}
dentaku_model = createDentakuModel();


function createViewControl()
{
    //フォーム作成。
    var form = document.createElement( "form" );
    form.className = "top";
    document.body.appendChild( form );

    //top
     var top = document.createElement( "table" );
     top.className = "top";
     form.appendChild( top );

     var tr = null;


     //テキストボックス
     function textbox()
     {
	 var element =  document.createElement( "input" );
	 element.type = "text";
	 element.value = 0;
	 element.size = "100";
	 element.readOnly = true;
	 element.className = "text"
	 var tde = td()
	 tde.colSpan = 4;
	 tde.appendChild( element );
	 return element;
     }

     var disp = null;

    function row()
    {
	var element = document.createElement( "tr" );
	element.className = "row";
	top.appendChild( element );
	tr = element;
    }

    function td( id )
    {
	var tde = document.createElement( "td" ); 
	tde.className ="cell";
	tde.id = id;
	tr.appendChild( tde );
	return tde;
    }

     function out()
     {
	 disp = textbox();
     }

     var debug_disp = null;
     function debug()
     {
	 debug_disp = textbox();
     }

     function key( ch )
     {
	 var element = document.createElement("input");
	 element.className = "button";
	 element.type = "button";
	 element.value = ch;
	 td( ch ).appendChild( element );
	 return element;
     }

     function refresh()
     {
	 disp.value = dentaku_model.getDisp();
	 debug_disp.value = dentaku_model.toString();
     }
    

    function sp(){ 
	tr.appendChild( td() );
    }

    function c() { key( "C" ).onclick = function(){ dentaku_model.clear()  ; refresh(); } }
    function ac() { key( "AC" ).onclick = function(){ dentaku_model.all_clear()  ; refresh(); } }
    function d() { key( "/" ).onclick = function() { dentaku_model.divide(); refresh(); } }
    function m() { key( "*" ).onclick = function() { dentaku_model.multi() ; refresh(); } }
    function s() { key( "-" ).onclick = function() { dentaku_model.sub()   ; refresh(); } }
    function a() { key( "+" ).onclick = function() { dentaku_model.add()   ; refresh(); } }
    function e() { key( "=" ).onclick = function() { dentaku_model.equal() ; refresh(); } }
    function p() { key( "." ).onclick = function() { dentaku_model.point() ; refresh(); } }
    function n( num ) { key( num ).onclick = function() { dentaku_model.number( num ) ; refresh(); } }
    function pm() { key( "+/-" ).onclick = function() { dentaku_model.minus() ; refresh(); } }
    
    //レイアウト
    {
	row(); out() ;
	row(); c()   ; ac()  ; pm()  ; d(); 
	row(); n( 7 ); n( 8 ); n( 9 ); m(); 
	row(); n( 4 ); n( 5 ); n( 6 ); s(); 
	row(); n( 1 ); n( 2 ); n( 3 ); a(); 
	row(); n( 0 ); p()   ; e(); 
	//row(); debug();

	var zero = document.getElementById( 0 );
	zero.colSpan =  2;
    }
    refresh();
}

window.onload = function()
{
    createViewControl();
}

</script>
</head>
<body>
</body>
</html>

そんじゃまた。

軽い気持ちで有名になりたいのでクリックをお願いします!

Javascriptで電卓を作ってみた。ソース付き。” への11件のコメント

  1. 付け焼き刃で修正しました。
    isInput = false; //←追加
    は意図していない動作になります。

    – ,9, 1 と入力すると、 -91 になるようにしたかったのです。
    currentがプラスのときとマイナスのときで場合分けをしました。

    if( 0 <= current )
    {
        //plus
        if( 0 < point ){
    	current += num * Math.pow( 10, -point );
    	point++;
        }
        else {
    	current = current * 10;
    	current += num;
        }
    }
    else
    {
        //minus
        if( 0 < point ){
    	current -= num * Math.pow( 10, -point );
    	point++;
        }
        else
        {
    	current = current * 10;
    	current -= num;
        }
    }
    
  2. バグっていましたが、-3を入力するときは、3 , +/- の順で入力してください。

  3. おぉ、本当ですね、意図しない値になりました。
    ご指摘ありがとうございます!
    修正いたします!

    -3を-, 3 の順に入力することを想定されていますか?
    もしそうだと、内部的に 0 - 3 と同じ入力なので、区別ができません。
    区別するために、もう1個ボタンを増やすか、オールクリアの直後だけ、-3にするとか考えられますが、+/-ボタンがあるので追加をするメリットがありません。

  4. 現況だと number Key > +/- Key > number Keyと打つと、変な値が入ってしまします。例えば、 2 > +/- で-2となり、その後にユーザーが3を打つと、私の所では何故か -17 が表示されていまいます。これは、
    m.minus = function()で、
    {
    isInput = false; //←追加
    current = -current;
    }
    とする事で回避で来ました。

    例えば-3を先に打ち込みたいときに、最初-が表示されない(計算などの結果は正しい)のは、何とか解消出来ないでしょうか?

  5. m.calc()の使い方が興味深いです。モデルの方では、この部分が実はキモなのですね。

  6. Javascriptマスターみたいな人から見れば、無駄が多いかもしれません。
    ぜひ、無駄のない電卓プログラムを提示してください!

  7. ピンバック: プログラマになるにはどうすればいいですか? | ヤマヤタケシのブログ

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です