1 月 12th, 2009
| Categories: JavaScriptの基本, 計算
| Tags: , ,

前回、JavaScriptで計算する場合の記事を書いた。

その後、例えば小数点の値同士を乗算するような場合に、計算結果がおかしくなる現象が出ていることに気が付いた。

例)
33.3333 x 3.3333 = 111.10988889 とならなければならないのに、
33.3333 x 3.3333 = 111.10988889000001 となってしまうようなエラーである。

で、その原因を調べてみたら以下のようなページがあった。
演算誤差について(10進数と2進数) (unibon)

上記のサイト内の以下の記事に掲載されている関数を加えることで、対応した。
JavaScript で演算結果の誤差を目立たなくする (unibon)

実際のコードは以下の通り。


// JavaScript Document
// 品目毎の金額を計算
function calc_price(){
	var num1 = document.frm1['num1'].value;
	var num2 = document.frm1['num2'].value;
	var num3 = document.frm1['num3'].value;
	var num4 = document.frm1['num4'].value;
	var num5 = document.frm1['num5'].value;
	var num6 = document.frm1['num6'].value;
	var num7 = document.frm1['num7'].value;

	var tanka1 = document.frm1['tanka1'].value;
	var tanka2 = document.frm1['tanka2'].value;
	var tanka3 = document.frm1['tanka3'].value;
	var tanka4 = document.frm1['tanka4'].value;
	var tanka5 = document.frm1['tanka5'].value;
	var tanka6 = document.frm1['tanka6'].value;
	var tanka7 = document.frm1['tanka7'].value;	

	if((num1 != "0") || (tanka1 != "0")){
		// 数量1や単価1が数値以外の場合は、金額1に0をセット
		if((isNaN(num1)) || (isNaN(tanka1))){
			document.frm1['price1'].value = 0;
		}
		// eval() とは、数式を数値に変換するメソッド
		//document.frm1['price1'].value = eval(num1) * eval(tanka1);
		else{
			document.frm1['price1'].value = mul(num1,tanka1);
		}
	}

	if((num2 != "0") || (tanka2 != "0")){
		// 数量2や単価2が数値以外の場合は、金額2に0をセット
		if((isNaN(num2)) || (isNaN(tanka2))){
			document.frm1['price2'].value = 0;
			return;
		}else{
			document.frm1['price2'].value = mul(num2,tanka2);
		}
	}

	if((num3 != "0") || (tanka3 != "0")){
		// 数量3や単価3が数値以外の場合は、金額3に0をセット
		if((isNaN(num3)) || (isNaN(tanka3))){
			document.frm1['price3'].value = 0;
		}else{
			document.frm1['price3'].value = mul(num3,tanka3);
		}
	}

	if((num4 != "0") || (tanka4 != "0")){
		// 数量4や単価4が数値以外の場合は、金額4に0をセット
		if((isNaN(num4)) || (isNaN(tanka4))){
			document.frm1['price4'].value = 0;
		}else{
			document.frm1['price4'].value = mul(num4,tanka4);
		}
	}

	if((num5 != "0") || (tanka5 != "0")){
		// 数量5や単価5が数値以外の場合は、金額5に0をセット
		if((isNaN(num5)) || (isNaN(tanka5))){
			document.frm1['price5'].value = 0;
		}else{
			document.frm1['price5'].value = mul(num5,tanka5);
		}
	}

	if((num6 != "0") || (tanka6 != "0")){
		// 数量6や単価6が数値以外の場合は、金額6に0をセット
		if((isNaN(num6)) || (isNaN(tanka6))){
			document.frm1['price6'].value = 0;
		}else{
			document.frm1['price6'].value = mul(num6,tanka6);
		}
	}

	if((num7 != "0") || (tanka7 != "0")){
		// 数量7や単価7が数値以外の場合は、金額7に0をセット
		if((isNaN(num7)) || (isNaN(tanka7))){
			document.frm1['price7'].value = 0;
		}else{
			document.frm1['price7'].value = mul(num7,tanka7);
		}
	}

/*
 * 以下、Javascriptで小数を含む演算結果の誤差を目立たなくする関数
 * URL http://www.geocities.co.jp/SiliconValley/4334/unibon/javascript/decimalcalculate.html
 *
 */
function decimalOperator(t, a, b) {
    var x = "" + a;
    var p = x.indexOf(".");
    var m;
    if (p >= 0) {
        m = x.length - (p + 1);
    } else {
        m = 0;
    }

    var y = "" + b;
    var q = y.indexOf(".");
    var n;
    if (q >= 0) {
        n = y.length - (q + 1);
    } else {
        n = 0;
    }

    var k;
    var c;
    if (t == "+") { // add
        k = Math.max(m, n);
        c = (a - 0) + (b - 0);
    } else if (t == "-") { // sub
        k = Math.max(m, n);
        c = (a - 0) - (b - 0);
    } else if (t == "*") { // mul
        k = m + n;
        c = (a - 0) * (b - 0);
    } else {
        return null;
    }

    var z = "" + c;
    if (x.indexOf("e") >= 0 || y.indexOf("e") >= 0 || z.indexOf("e") >= 0) {
        return c;
    }

    var r = z.indexOf(".");
    var d;
    if (r >= 0) {
        var u = z.substring(r + 1 + k, r + 1 + k + 1);
        d = z.substring(0, r + 1 + k);
        if (u >= "5") {
            var e = "";
            var w = 1;
            for (var i = d.length - 1; i >= 0; i--) {
                var g = d.substring(i, i + 1);
                if (g >= "0" && g = 10) {
                        h -= 10;
                        w++;
                    }
                    e = ("" + h) + e;
                } else if (w > 0 && (g == "+" || g == "-")) {
                    e = g + ("" + w) + e;
                } else {
                    e = g + e;
                }
            }
            d = e;
        }
    } else {
        d = z;
    }
    var f = d - 0;
    return f;
}

function add(a, b) {
    return decimalOperator("+", a, b);
}

function sub(a, b) {
    return decimalOperator("-", a, b);
}

function mul(a, b) {
    return decimalOperator("*", a, b);
}

}

フォームからは以下のようにして、上記のコードを呼び出す。(※PHPのSmartyを使用)


<form name="frm1" method="post" action="index.php">
<!-- 注文内容 -->
<table cellspacing="0" class="chumon-naiyou">
<tr>
<td class="chumon-form-category" colspan="6">注文内容</th>
</tr>
<tr>
<th scope="col" class="chumon-form-2" colspan="2">品名</th>
<th scope="col" class="chumon-form-2">数量</th>
<th scope="col" class="chumon-form-2">単価</th>
<th scope="col" class="chumon-form-2">金額</th>
</tr>
<!-- 1 -->
<tr>
<td class="chumon-form-2-left"><span class="red">※</span>1</td>
<td class="chumon-form-2">
<input type="text" name="hinmei1" size="40" value="{$hinmei1|escape}" />
</td>
<td class="chumon-form-2">
<input type="text" name="num1" size="8" maxlength="10" value="{$num1|escape}" onchange="calc_price();" />
</td>
<td class="chumon-form-2">
<input type="text" name="tanka1" size="8" maxlength="10" value="{$tanka1|escape}" onchange="calc_price();" />
</td>
<td class="chumon-form-2">
<input type="text" name="price1" size="16" maxlength="18" value="{$price1|escape}" />
</td>
</tr>
<!-- 以下、省略 -->
</table>
<input type="submit" value="確 認" />
</form>
11 月 25th, 2008
| Categories: Form, ajax, 計算
| Tags: , , ,

現在開発しているオンライン請求書発行システムで、住所入力の負担を減らすために、郵便番号から住所を検索して自動で表示させる機能をPHPで開発していた。

しかしこれだと、「郵便番号を入力→検索ボタン」のフォームと、「検索結果を反映し、住所を表示させる」フォームの2つのフォームが必要となり、ユーザビリティー的にもわかりにくい部分があった。

また、商品毎に単価x数量=合計金額を表示させる場合でも、PHPのみで完結させようとする場合、その都度「計算」ボタンをクリックさせる必要があるなど使いずらい。

そこで、「郵便番号から住所を検索して自動で表示させる」機能と、「商品毎に単価x数量=合計金額を表示させる」機能にJavaScriptを使用することにした。

そもそも、Javascriptに関しては本格的に学習をしたことがないし、Ajaxも少しかじっただけで、これまで本気で取り組もうと思ったことがなかった。

言い訳になるが、PHPでZendFramework等をマスターすることを優先し、クライアントサイドの技術に関しては、あくまでオマケ的に考えていた部分があった。
(そもそも、余裕ができればFLEXに取り組もうと考えていた)

以下、実装の方法。

■「郵便番号から住所を検索して自動で表示させる」機能
ググったところ、下記のサイトがヒットした。
http://kawa.net/works/ajax/ajaxzip2/ajaxzip2.html#download
郵便番号辞書データも2008年11月2日付けの新しいデータが用意されていたので、今回はこれをそのまま使用することにした。

■「商品毎に単価x数量=合計金額を表示させる」機能
当初Ajaxのいいサンプルがないか探してみたが、すぐに見つからなかったのと、そもそも乗算程度の機能であれば、Javascriptのみで充分なことに気がつき、ネットと書籍で調べて以下の記述で対応した。


<!--
// 合計金額を計算
function calc(CL){
// ビルトイン関数「eval()」を使うことで、フォームに入力された文字列の内容を
// 数値や演算子として評価し、Javascriptで計算できるようにしている。
CL.goukei.value = eval(CL.num.value)*eval(CL.tanka.value);
}
-->

<form method="post" action="">
<table cellspacing="0">
<tr>
<th scope="col" width="240">品名</th>
<th scope="col" width="80">数量</th>
<th scope="col" width="80">単価(円)</th>
<th scope="col" width="100">合計金額(円)</th>
</tr>
<tr>
<td><input type="text" name="hinemi" size="10" /></td>
<td><input type="text" name="num" size="5" maxlength="8" value="0" onChange="calc(this.form)" /></td>
<td><input type="text" name="tanka" size="5" maxlength="8" value="0" onChange="calc(this.form)" /></td>
<td><input type="text" name="goukei" size="5" maxlength="8" /></td>
</tr>
</table>
</form>

やっぱり、JavaScriptももう少しできるようになる必要があるので、この機会にBlogに色々メモっていくことにする。