不便で面倒なMath周りでエイリアスや追加モジュールで省力化を図る[オレ得JavaScriptメモ]

JAVASCRIPT

もはや何枚目かわからなくなったオレ得JavaScriptメモです。

人間40歳を過ぎると一気に体力の衰えを感じるもんです。明日の健康診断が心配です。夏には42になるごろどくですどうも。

40代になるとマスをかくにも一苦労です。変な意味じゃないですよ。Math.です。

とにかくMath.~って書くのが煩わしい

「Math書くの面倒くさい」ってもう一言で全てなんですが、算術系のスクリプト書いてるとひたすらMathなんですよ。Math.cos()とかMath.max()とかMath.sqrt()とか。書いてるうちにだんだん嫌になるんです。

JSをhtml周りの体裁をごにょごにょしてたり動的にコンテンツを表示する目的で使ってる界隈の人にはあんまり実感として伝わらないかもしれませんが。

例えば、直角三角形の斜辺cと1辺aがわかってるとき、他辺b求めるのに「(斜辺の二乗-1辺の二乗)のルート」なんてのは、数式で書けば

程度のことををJSで普通に書くと


b = Math.sqrt(Math.pow(c,2)-Math.pow(a,2));

とか


b = Math.pow((Math.pow(c,2)-Math.pow(a,2)),0.5);

なんて書かなきゃいけないわけ。3回もマスかいてるわけ。これ一本なら別にどうってことないけど、もっと複雑な式を数十本も数百本も書かなきゃいけないようなスクリプトだともうマスかきすぎてげっそりするわけ。そういう時にどうするかってことなんですが。

var m = Math;

「var m = Math;」です。mにMathを代入します。これで3文字省略できます。以上。

バカですか?すみません。mはグローバルには置きません。当たり前ですね。数理的処理は普通何らかの関数作ってそれを呼び出す形で使うので、関数のスコープだけで生きるmを宣言してMathのエイリアスとして使います。

関数スコープごとにいちいち「var m = Math;」って書いてたらせっかくの省略分が無駄になるので、関連する関数群を一つのオブジェクトのメソッドとして定義して、そのオブジェクトのスコープでmを定義すればいいのではないでしょうか。

これが結構バカにできなくて、Mathメソッドやオブジェクトが1,000個くらい出現すると約3,000文字の節約になります。あとMathをmathとかMayhとかNathとかタイポすることも確実に防げます。

mだけにしてもmをnとタイポする人、今日は定時で帰ってご飯をよく噛んで食べて、お風呂はやや低い温度で長めに入り、はやくお布団に入って睡眠をたっぷりとりましょう。

mの処理速度とか

forループでMath.PIを10,000回加算し続けるのと同じくm.PIを10,000回加算し続けるのを10,000回行ったときの10,000加算当たりの平均時間を計測したら、当方環境(Win7/chrome49/i7 3.4GHz)でMathが0.0395ms、mが0.0385ms前後と若干mの方が早かったです。

インタプリタで行処理の文字数が違う分の差だと思います。mがMathより遅くなることはないんじゃないかなーと思います。

いずれにしてもm=Mathは速度求めてやってることじゃないので、「遅くならない」ことが確認できれば十分以上です。

あとどうでもいいですが、「var」忘れるとmがグローバルになるので泣きます。だいたいそういう時は1文字変数でテストしてて、かつmとか使ってたりするんですよね。気を付けましょう。

不足のメソッドは追加しよう

あと数値の丸めとかね。組み込みのMath.round()では小数点固定桁処理しかできず非力すぎる。任意桁の丸めするのにいちいち必要桁分10のべき乗かけて丸めて桁戻すとかやってられないじゃないですか。

割りと単純な関数(10行未満)なので、必要な時に再々々々々々々発明とかしちゃう、しかも発明の度に識別子表記が揺れたりするので、これは非常に無駄なうえに間違いの原因になったりします。

そういう場合は頻用しそうな数値処理をMathオブジェクトのメソッドとして追加してしまうのはどうでしょう。っていうか必要な人は多分似たようなことやってると思います。Mathオブジェクトじゃなくて追加オブジェクトみたいな形をとってるかもしれませんけど。

ということで以下、Mathオブジェクトの組み込みプロパティ・メソッドのおさらい、あと私が追加で作ったメソッド群の紹介なんかをしたいと思います。

組み込みプロパティ・メソッドについてはあらゆ氏が

JavaScriptの日本語リファレンス(初心者向け)

にやや噛み砕いて書いてたり

Math – JavaScript | MDN

に詳しいので、ここではさらっと触れるにとどめます。

Mathオブジェクトの組み込みプロパティ

Mathオブジェクトのプロパティは算術を行う時に頻用される定数値を格納した静的なNumber型オブジェクトです。いずれも無理数であるため当然数学的な意味での厳密解とは異なり、一定の精度で表現可能な桁数までの値を返します。

E

ネイピア数(オイラー数)を返します。値は2.718281828459045ぐらいです。

LN2

<9>2の自然対数(ネイピア数eを底とする2の対数)を返します。値は0.6931471805599453ぐらいです。

LN10

10の自然対数(ネイピア数eを底とする10の対数)を返します。値は2.302585092994046ぐらいです。

LOG2E

2を底としたeの対数を返します。値は1.4426950408889634ぐらいです。

LOG10E

10を底としたeの対数を返します。値は0.4342944819032518ぐらいです。

PI

円周率を返します。値は3.141592653589793ぐらいです。

SQRT1_2

1/2の平方根を返します。値は0.7071067811865476ぐらいです。

mSQRT2

2の平方根を返します。値は1.4142135623730951ぐらいです。

Mathオブジェクトの組み込みメソッド

Mathオブジェクトのメソッドは引数を算術処理した結果を数値型オブジェクトとして返します。

よく使われていそうな数値の丸め処理などは、実は思わぬところで落とし穴があったりします(特に負数を含む場合)。この辺りは

丸く収まらないJavaScriptの数値丸め~round、ceil、etc… | 56docブログ

にも書きましたので合わせて参照いただければ。

abs(num)

引数numの絶対値を返します。

acos(num)

引数numのアークコサインをラジアンで返します。アークコサインはコサインの逆関数であって、逆数ではありません。

asin(num)

引数numのアークサインをラジアンで返します。アークサインはサインの逆関数であって、逆数ではありません。

atan(num)

引数numのアークタンジェントをラジアンで返します。アークタンジェントはタンジェントの逆関数であって、逆数ではありません。

atan2(num1,num2)

引数の比率(num1/num2)のアークタンジェントをラジアンで返します。アークタンジェントの解説については同上。

ceil(num)

引数num以上の最小の整数を返します。正の無限大方向への丸めです。いわゆる「切上げ」とは異なります。

絶対値に注目すると引数が正の値と負の値の場合では挙動が異なるように見えるので注意が必要です。


mCeil(1.4);//2
mCeil(-1.4);//-1
mCeil(1.5);//2
mCeil(-1.5);//-1
mCeil(1.6);//2
mCeil(-1.6);//-1

cos(rad)

元のメソッド:Math.cos(rad)

引数radのコサイン(余弦)を返します。radはラジアンで指定します。

exp(num)

ネイピア数eのべき乗、つまりeのnum乗を返します。

floor(num)

引数num以下の最大の整数を返します。負の無限大方向への丸めです。いわゆる「切捨て」とは異なります。

絶対値に注目すると引数が正の値と負の値の場合では挙動が異なるように見えるので注意が必要です。


mFloor(1.4);//1
mFloor(-1.4);//-2
mFloor(1.5);//1
mFloor(-1.5);//-2
mFloor(1.6);//1
mFloor(-1.6);//-2

log(num)

引数numの自然対数(底をeとするnumの対数)を返します。

max(num1,num2,num3…)

与えられた引数群num1,num2,num3…のうちの最大の値を返します。

min(num1,num2,num3…)

与えられた引数群num1,num2,num3…のうちの最小の値を返します。

pow(base,exponent)

任意の値のべき乗(baseのexponent乗)を返します。

random()

0以上1未満(1は含まない)の範囲で疑似乱数を返します。引数は不要です。

なお、JavaScriptにおいては疑似乱数のシード値は現在時刻で固定され、変更することはできない模様。

round(num)

引数numと、num以下の最大の整数との差が0.5未満の場合は負の無限大方向へ、差が0.5以上の時は正の無限大方向へ丸めます。いわゆる「四捨五入」に似てますが負数では若干ニュアンスが違います。

小数点以下の端数が0.5ちょうどの時は正の無限大方向に丸めますから、負の値を引数に指定して、かつ端数が0.5ちょうどだった場合、絶対値に注目すると正の場合と挙動が異なるように見えるので注意が必要です。


mRound(1.4);//1
mRound(-1.4);//-1
mRound(1.5);//2
mRound(-1.5);//-1
mRound(1.6);//2
mRound(-1.6);//-2

sin(rad)

引数radのサイン(正弦)を返します。radはラジアンで指定します。

sqrt(num)

引数numの正の平方根を返します。返り値が複素数になるような場合、つまり引数が負の場合、NaNを返します。

tan(rad)タンジェント

引数radのタンジェント(正接)を返します。radはラジアンで指定します。

追加メソッド

Mathオブジェクトの組み込みメソッドだけでは算術的にはかなり限られたことしかできないので、都度関数を自作しなければなりませんが、なかでも繰り返し利用されそうなものをライブラリやモジュールとして用意しておくと、作業準備に無駄に時間をかけずに済むと思います。

中でも弧度法/度数法の角度相互変換、正割・余割・余接、有効数字丸め等は自然科学・工学系の分野でちょっとしたルーチン組むにも必須と思われますが組み込みではこぼれていたりします。

私個人の守備範囲では今のところ使わないですが、双曲線関数も一応作ってみました。そのうち使うことになるかもしれませんので。ライブラリファイルのサンプルはとりあえず

mathplus1.0.js

mathplus1.0.min.js

に置いておきます。ダウンロードして使用するときは、使用するコードの前でscriptタグで事前に読み込んでください。いずれのメソッドもMathオブジェクトのメソッドとして追加されますので、組み込みメソッドと同様Math.○×△~()という形で使用します。

以下、追加作成したメソッドについて簡単に解説します。

degToRad(deg)

引数degを度数法角度ディグリー(°)から弧度法角度ラジアンに変換した値を返します。

返り値の演算はdeg÷180×πで行っています。


Math.degToRad(-450);//-7.853981633974483(≒-5Π/2ラジアン)
Math.degToRad(-360);//-6.283185307179586(≒-2πラジアン)
Math.degToRad(-270);//-4.71238898038469(≒-3π/2ラジアン)
Math.degToRad(-180);//-3.141592653589793(≒-πラジアン)
Math.degToRad(-90);//-1.5707963267948966(≒-π/2ラジアン)
Math.degToRad(0);//0(=0ラジアン)
Math.degToRad(90);//1.5707963267948966(≒π/2ラジアン)
Math.degToRad(180);//3.141592653589793(≒πラジアン)
Math.degToRad(270);//4.71238898038469(≒3π/2ラジアン)
Math.degToRad(360);//6.283185307179586(≒2πラジアン)
Math.degToRad(450);//7.853981633974483(≒5Π/2ラジアン)

radToDeg(rad)

引数radを弧度法角度ラジアンから度数法角度ディグリー(°)に変換した値を返します。

返り値の演算はrad÷π×180で行っています。


Math.radToDeg(-5*Math.PI/2);//-450
Math.radToDeg(-2*Math.PI);//-360
Math.radToDeg(-3*Math.PI/2);//-270
Math.radToDeg(-Math.PI);//-180
Math.radToDeg(-Math.PI/2);//-90
Math.radToDeg(0);//0
Math.radToDeg(Math.PI/2);//90
Math.radToDeg(Math.PI);//180
Math.radToDeg(3*Math.PI/2);//270
Math.radToDeg(2*Math.PI);//360
Math.radToDeg(5*Math.PI/2);//450

rad2PI(rad)

引数radを0より大きく2π以下の値(0<rad≦2π)に変換した値を返します。

三角関数を含む周期関数で基本周期での演算を行う場合などに使用します。返り値を用いた演算時にゼロ除算を避けるために返り値には0を含んでいません。

そのため、引数が0、または2πの整数周期倍の場合は2πを返します。

周期関数での利用を前提としているので、引数が負の場合は正の値になるよう正周期方向にオフセットした値を返します。絶対値演算ではありません。


Math.rad2PI(-5/2*Math.PI);//4.71238898038469(≒3π/2ラジアン、度数法の270°相当)
Math.rad2PI(-2*Math.PI);//6.283185307179586(≒2πラジアン、度数法の360°相当)
Math.rad2PI(-3/2*Math.PI);//1.5707963267948966(≒π/2ラジアン、度数法の90°相当)
Math.rad2PI(-Math.PI);//3.141592653589793(≒πラジアン、度数法の180°相当)
Math.rad2PI(-1/2*Math.PI);//4.71238898038469(≒3π/2ラジアン、度数法の270°相当)
Math.rad2PI(0);//6.283185307179586(≒2πラジアン、度数法の360°相当)
Math.rad2PI(1/2*Math.PI);//1.5707963267948966(≒π/2ラジアン、度数法の90°相当)
Math.rad2PI(Math.PI);//3.141592653589793(≒πラジアン、度数法の180°相当)
Math.rad2PI(3/2*Math.PI);//4.71238898038469(≒3π/2ラジアン、度数法の270°相当)
Math.rad2PI(2*Math.PI);//6.283185307179586(≒2πラジアン、度数法の360°相当)
Math.rad2PI(5/2*Math.PI);//1.5707963267948966(≒π/2ラジアン、度数法の90°相当)

roundSig(num,digit)

任意の値(引数num)を指定の有効桁数(引数digit)に丸めた値を返します。指定有効桁数の1つ下の桁を四捨五入します。

引数numが正の場合でも負の場合でも絶対値に対して四捨五入を行った後符号を振りなおしているので、同値整数丸めを行ったときにround()メソッドとは異なる結果になる場合があります。

引数numが0の場合、0を返します。引数digitが0以下の場合および引数digitが整数でない場合、NaNを返します。

引数digitで指定した有効桁数が引数numの桁数に満たない場合、有効桁未満の整数桁は0埋めされます。


Math.roundSig(3.14159265,3);//3.14
Math.roundSig(3.14159265,6);//3.14159
Math.roundSig(3.14159265,0);//NaN
Math.Math.roundSig(31415.9265,3);//31400
Math.roundSig(31415.9265,6);//31415.9
Math.roundSig(-3.14159265,3);//-3.14
Math.roundSig(-3.14159265,6);//-3.14159
Math.roundSig(-3.14159265,0);//NaN
Math.roundSig(31415.9265,3.2);//NaN
Math.roundSig(31415.9265,-6);//NaN

roundJis(number,digit)

roundSigと同様の有効数字丸めの結果を返すメソッドですが、丸めの方法が絶対値に対する四捨五入ではなく「最近接偶数丸め(またはJIS丸め、銀行丸め、とも)」である点が異なります。

最近接偶数丸めを簡単にごく説明すると「丸めの桁がちょうど5のとき、有効桁が偶数になるように丸める」という端数処理です。丸めの桁が5より大きい場合、または5より小さい場合については四捨五入の場合と変わりません。

ある統計量の母集団の個々のデータについて端数がランダムに表れる場合、1と9、2と8、3と7、4と6はそれぞれ切り捨て・切上げされるので誤差がプラス側マイナス側一様に分散されます。

また端数が0の場合は端数処理を行った結果と行わない結果が同じであるため誤差の分散に影響を及ぼしませんが、5を四捨五入のルール通り単純に切上げとするとこれだけは対になる値を持たず、誤差は常にプラス側のバイアスを持つことになります。

よって統計量や通貨計算で誤差累積を嫌う場合に有効な丸めの方法と言えます。

0やNaNを返す条件、負数の処理および0埋めについてもroundSigと同様です。


Math.roundJis(1.14,2);//1.1:有効数字四捨五入と同様(端数切捨て)
Math.roundJis(1.15,2);//1.2:有効数字四捨五入と同様(端数がちょうど5で切上げると1.2(偶数)→そのまま)
Math.roundJis(1.16,2);//1.2:有効数字四捨五入と同様(端数切上げ)
Math.roundJis(1.24,2);//1.2:有効数字四捨五入と同様(端数切捨て)
Math.roundJis(1.25,2);//1.2:有効数字四捨五入と異なる(端数がちょうど5で切上げると1.3(奇数)→1.2(偶数)になるよう丸める)
Math.roundJis(1.26,2);//1.3:有効数字四捨五入と同様(端数切上げ)

最近接偶数丸めについては端数処理 – Wikipediaも参照ください。

またこの丸めの方法は日本工業規格(JIS)で制定されていますので

日本工業標準調査会:データベース検索-JIS検索

から規格番号「Z8401」または規格名称「数値の丸め方」で検索して規格標の内容を確認してみてください。なお、JIS規格による最近接偶数丸めの対象は本来正の値に限ったものですが、本メソッドでは運用の利便性-例えば正数を減算した場合と負数を加算した場合の結果が同じになるようにするなど-を考え絶対値に対して同様の処理を行ったあと、符号を付して値を返すようにしています。

logReg(num)

引数numの常用対数(底が10であるnumの対数)を返します。

引数numが0の場合は負の無限大(-Infinity)を、負の場合はNaNを返します。


Math.logReg(10);//1
Math.logReg(100);//2
Math.logReg(0.001);//-3
Math.logReg(0);//-Infinity
Math.logReg(-10);//NaN

logAny(num1,num2)

任意の値num1を底とするnum2の対数を返します。

引数num1が0の場合は-0を、引数num2が0の場合は負の無限大(-Infinity)を返します。

num1、num2のいずれか、または両方が負の場合はNaNを返します。


Math.logAny(10,100);//2
Math.logAny(2,32);//5
Math.logAny(7,1);//0
Math.logAny(0,1);//-0
Math.logAny(13,0);//-Infinity
Math.logAny(2,-4);//NaN
Math.logAny(-2,4);//NaN
Math.logAny(-2,-4);//NaN

sec(rad)

引数radのセカント(正割)を返します。radはラジアンで指定します。

セカントはコサイン(余弦)の逆数と等値です。


Math.sec(0);//1
1/Math.cos(0);//1
Math.sec(Math.PI/2);//16331239353195370
1/Math.cos(Math.PI/2);//16331239353195370
Math.sec(Math.PI);//-1
1/Math.cos(Math.PI);//-1
Math.sec(3*Math.PI/2);//-5443746451065123
1/Math.cos(3*Math.PI/2);//-5443746451065123
Math.sec(2*Math.PI);//1
1/Math.cos(2*Math.PI);//1

sec(Math.PI/2)とsec(3*Math.PI/2)、およびこれらの整数周期の値は、数学的厳密値としては正の無限大であるべきですが、有限桁で表現可能な大きい正の値及び負の値となっています。

これは、Math.cos(Math.PI/2)、Math.cos(3*Math.PI/2)が本来数学的には0であるべきところを、Math.PI()の値が有限桁の精度の数値としてしか表現できないため、それぞれ0ではない極小の正の値および負の値となるという性質(演算上の誤差)をそのまま引き継いでいるためです。

メソッド内部で引数チェックは可能ですが、汎用的な算術メソッドとしてMath.cos()と整合を図るため、あえてそのまま誤差値を残しています。

数学的厳密さが必要な場合は、引数として渡す値が「Math.PI/2の(整数+0.5)倍」でInfinityに符号をつけて返す例外処理を行い、そうでない場合にsec()を適用するようにします。

当然、組み込み関数cos()で同様に例外処理として0を返すことは可能です。いずれにしてもその他の中間値では有限の桁精度しか担保せず、真の値に対して誤差を持つことは何も変わりませんが。

cosec(rad)

引数radのコセカント(余割)を返します。radはラジアンで指定します。

コセカントはサイン(正弦)の逆数と等値です。


Math.cosec(0);//Infinity
1/Math.sin(0);//Infinity
Math.cosec(Math.PI/2);//1
1/Math.sin(Math.PI/2);//1
Math.cosec(Math.PI);//8165619676597685
1/Math.sin(Math.PI);//8165619676597685
Math.cosec(3*Math.PI/2);//-1
1/Math.sin(3*Math.PI/2);//-1
Math.cosec(2*Math.PI);//-4082809838298842.5
1/Math.sin(2*Math.PI);//-4082809838298842.5

引数radが0でないMath.PIの整数倍で有限桁の極大数となりInfinityとならない(誤差を持つ)理由はセカントの場合と同様です。

本メソッドおよび組み込み関数sin()で例外処理を行う場合「Math.PIの整数倍」で符号をつけて返します。

cot(rad)

引数radのコタンジェント(余接)を返します。radはラジアンで指定します。

コタンジェントはタンジェント(正接)の逆数と等値です。


Math.cot(0);//Infinity
1/Math.tan(0);//Infinity
Math.cot(Math.PI/2);//6.123233995736766e-17
1/Math.tan(Math.PI/2);//6.123233995736766e-17
Math.cot(Math.PI);//-8165619676597685
1/Math.tan(Math.PI);//-8165619676597685
Math.cot(3*Math.PI/2);//1.83697019872103e-16
1/Math.tan(3*Math.PI/2);//1.83697019872103e-16
Math.cot(2*Math.PI);//4082809838298842.5
1/Math.tan(2*Math.PI);//4082809838298842.5

引数radが0でないMath.PIの整数倍で有限桁の極大数となりInfinityとならない、またそれ以外のMath.PI/2の整数倍で有限桁の極小数となり0とならない(誤差を持つ)理由はセカントやコセカントの場合と同様です。

asec(num)

引数numのアークセカントをラジアンで返します。アークセカントはセカントの逆関数であって、逆数ではありません。

アークセカントは引数numの逆数のアークコサインと等値です。

引数numが0の場合、NaNを返します。

acosec(num)

引数numのアークコセカントをラジアンで返します。アークコセカントはコセカントの逆関数であって、逆数ではありません。

アークコセカントは引数numの逆数のアークサインと等値です。

引数numが0の場合、NaNを返します。

acot(num)

引数numのアークコタンジェントをラジアンで返します。アークコタンジェントはコタンジェントの逆関数であって、逆数ではありません。

アークコタンジェントは引数numの逆数のアークタンジェントと等値です。

引数numにInfinityを渡した場合な0を、-Infinityを渡した場合は-0を返します。また、引数に0を渡した場合はMath.PI/2を、-0を渡した場合は-Math.PI/2を返します。

したがって、正負の判別は可能です。

sinh(num)

引数numのハイパボリックサイン(双曲線正弦)を返します。定義式は以下の通り。

cosh(num)

引数numのハイパボリックコサイン(双曲線余弦)を返します。定義式は以下の通り。

 

tanh(num)

引数numのハイパボリックタンジェント(双曲線正接)を返します。定義式は以下の通り。

cosech(num)

引数numのハイパボリックコセカント(双曲線余割)を返します。ハイパボリックコセカントはハイパボリックサイン(双曲線正弦)の逆数です。

sech(num)

引数numのハイパボリックセカント(双曲線正割)を返します。ハイパボリックセカントはハイパボリックコサイン(双曲線余弦)の逆数です。

coth(num)

引数numのハイパボリックコタンジェント(双曲線余接)を返します。ハイパボリックコタンジェントはハイパボリックタンジェント(双曲線正接)の逆数です。

まとめ

あれだけマスかきたくないといったのにサンプルでマスかきまくってもう頭ぼーっとしてます。最後手抜きしましたすみません。手ヌキはしてません。

ライブラリのサンプルはコメントそのまんま残してあるのと不用品回収したの(minのほう)と一応両方用意しました。コメント付きのは改行もインデントも残ってるので何やってるかはそっちの中身見たらわかると思います。

あくまで自分用に作ったものですが、使いたい方はご自由にどうぞ。中身が中身だけに権利どうこう言うほどのもんでもないし。そのうちさらに必要そうなものを突っ込んで2.0あたりが出るかもしれないし出ないかもしれません。たぶん出ません。

どちらかというと平面図形の断面性能計算するライブラリのほうが今自分的に必要なのでまずはそっちをやっつけます。そちらの方が公開の可能性たぶん高いと思います。

いずれにしてもMath周り(というか数理的処理)はあまり便利そうな話を聞かないので、これでだれかもっといい物作ってくれるきっかけにでもなればと思います。んじゃまた。

コメント

タイトルとURLをコピーしました