JavaScriptで、できるかぎり小数演算の誤差を少なくする方法

[`evernote` not found]
[`livedoor` not found]
[`yahoo` not found]

JavaScriptにかぎらず、プログラミングでの小数を含む演算では誤差が発生してしまうことがよくあります。例えば、『0.2+0.1』という単純な足し算すら、『0.30000000000000004』という結果になってしまいます(Chromeで確認した場合)。その対応方法が改訂新版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用までに書いてあったのでメモしておきます。

対応方法といっても単純なことで、ようは小数をいったん小数の長さ分の10の累乗を掛けて整数にしてから演算し、それから10の累乗分した数を割って正しい値を取得するというもの。最後の10の累乗で割るときに誤差が発生してしまうのではないかと思いそうですが、割られる方の数が整数であれば誤差は発生しないようです(例えば、『12.35/10』の結果は『1.2349999999999999』となるけど、『1235/1000』の結果は『1.235』となる)。

というわけで、小数の演算に対応した関数を書いてみました。

足し算、引き算、掛け算、割り算の4つの関数がありますが、上半分はすべて同じです。本来ならこの部分は共通化しておいたほうがいいでしょうね。エラー処理は余計だったかもしれません(この処理がなくても、どのみち例外エラーが発生するのだし、throwで返すのではなく、0やnullを返すとかでもいいかも)。

エラー処理はまず引数が数値かどうかチェックし、その後に指数表記かどうかを調べています。

実行した結果は下記のとおりです(『//=>』より右側が実行結果)。

ところで今回、小数って英語でなんていうんだけと思って調べて、英語でdecimalということを初めて知りました。decimalというと、10進数というイメージがあるのだけど、小数という意味もあったのかと。どっちの意味で使ってるか分かりづらいことってないのだろうか。

JavaScriptでローカル変数と同じ名前のグローバル変数を呼び出す方法(環境問わず)

[`evernote` not found]
[`livedoor` not found]
[`yahoo` not found]

今回も改訂新版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用までで知ったことから。

昔も似たようなタイトルで投稿したことあるのですが(JavaScriptでローカル変数と同じ名前のグローバル変数を呼び出す方法 | while(isプログラマ))、これはフロントエンド(ウェブブラウザ)限定の方法。グローバル変数はWindowオブジェクトに属しているから、windowオブジェクトのプロパティとして呼び出せばいいという話でした。今回は環境を問わず、例えばnode.jsでも通じる話。

その前に、JavaScriptでの関数の指定方法は4つあります。

1つ目は下記のようにfunction命令で定義する方法。

2つ目は下記のようにFunctionオブジェクトのコンストラクターを経由して定義する方法。

3つ目は下記のように関数リテラルで定義する方法

4つ目はES2015で追加されたアロー関数で定義する方法

どれも、『func(123)』とするとコンソールに『123』と出力されますが、実はそれぞれ微妙に違うそうです。例えば、function命令は静的な構造で定義より上で呼び出す記述があっても実行されるのに対し、関数リテラルやFunctionコンストラクターは実行時に評価されるので、定義より上で呼び出す記述があればエラーとなる等。

で、今回その違いで初めて知ったのが、Functionコンストラクターで定義した関数内の変数は、定義したスコープ内に同じ変数名があったとしてもグローバル変数を参照するということ。Functionコンストラクターなんて普段使わないから知りませんでした。

というわけで、以下サンプル。

2つ目の即時関数で、globalFuncやglobalAppleを用意して、それぞれFunctionコンストラクターで定義した即時関数からfunc関数とapple変数を取得しています。この結果は、同じスコープ内にappleという変数とfuncという関数があるにも関わらず、『global:10』となりました。つまり、グローバル変数を取得しているわけです。

まあ、現在のスコープ内に定義されている変数名と同じ名前のグローバル変数を取得することなんてそうそうないとは思いますが、Functionコンストラクターで定義した関数の中の変数はグローバル変数を参照するということは覚えておいたほうがいいかもしれません。「スコープ内で同じ名前の変数が存在してるのに、何でエラーになるんだろう?」となりかねません(もちろん、エラーになる理由はグローバル変数に同名の変数がないから)。

まあでも、最初に紹介した本にも書いてありましたが、Functionコンストラクターでの関数宣言は利用しないほうがいいと思います。分かりづらいです。文字列変数を利用して生成できるというメリットはあるかもしれませんが、その変数が外部から参照している値を利用していたらセキュリティ的に問題になる可能性があります。

JavaScriptで多重ループを抜けるラベル構文について

[`evernote` not found]
[`livedoor` not found]
[`yahoo` not found]

最近、改訂新版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用までという本を読んでいるのですが、ES2015も含めたJavaScriptについて本格的に書かれてあって、とても分かりやすいです。

JavaScriptはある程度分かっていたつもりなのですが、それでもこういうまとまった本を読むと、何個かは知らなかった記述がでてきます(ES2015についてはほとんど知らないことばかりですが)。

今回はその中で、2.5.8に記載されていた多重ループを一気に抜けるラベル構文についてメモ。調べてみたら、よくある記述らしいんで、ブログに書こうか迷ったのですが、前に多重ループを抜けるときは匿名関数にして即時実行したほうがいいというようなことを書いてしまったので……。
for文の二重ループを一重ループにする方法 | while(isプログラマ)

で、上記記事に書いた多重ループの抜ける方法をラベル構文で書くと下記のようになる。

変更した箇所は、最初のfor文の前に『kuku』というラベルをつけ、多重ループを抜けるときには『break kuku;』とやるだけ。上で紹介した本が改行してあったので、ラベル指定の後は改行しましたが、『kuku: for(i=0;i<9;i++){』と改行しないほうが分かりやすいかも? このラベル構文はcontinue文でも利用できるとのことで、またswitch文の中でループを抜ける際にはラベル構文でなければならないとのこと(そりゃ、switch命令内でbreakとすると、switch命令を抜けるだけですしね)。 参考:break文とcontinue文でのラベル指定 – 繰り返し処理 – JavaScript入門

改訂新版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで
山田 祥寛
技術評論社
売り上げランキング: 4,391

JavaScriptで関数の引数名を取得する方法とその利用例

[`evernote` not found]
[`livedoor` not found]
[`yahoo` not found]

自分が今関わってるプロジェクトですが、AngularJSでサイトを作っていた人が別のプロジェクトに移動することになってとりあえず自分が引き継ぐことになりました。

AngularJSはちょっとだけ触ったことがあるのですが、正直よく分かっていません。Backbone.jsやReactはなんとなく分かるのですが、AngularJSはほとんど全く分かりません。で、案の定、コードを見てどのような動きをしているのか調べようと思っても全く分からず。特に、ある関数(確か、controllerの引数に含めた無名関数)の引数の順番がどうなっているのか全く分かりません。複数のファイルで同じような記述があったのですが、全く順番が異なるように思えました。いくら調べても分からなかったので、仕方なく尋ねると、「Angularが引数名で判断してるんです」。

はぁ? JavaScriptはある程度分かってるつもりの自分ですが、引数名を判断する方法があるなんて知らずに、驚きました。

調べてみると、すぐにその方法が見つかりました。単純に、関数自体のtoStringメソッドを呼んで、分解しているそうです。
参考:関数の引数名を取得する – Qiita

上記のgetParams関数は、引数に関数を入れると、その関数の引数名の配列を返すようになっているようです。replaceの中身の正規表現をよくよく見てみると、一つ目の|の左側(\/\/.*$)が行コメント、二つ目の|の左側(\/\*[\s\S]*?\*\/)が複数行コメント、二つ目の|の右側(\s)がスペースやタブや改行文字などのホワイトスペース文字を表しており、フラグにはg(グローバルサーチ)とm(複数行検索)を指定しているそうです。その正規表現にヒットした文字をまず無くしているわけですね。

続いて、matchを使って、丸括弧内を検索して引数のみを取得しているようです(ちなみに、インデックスを0にするとカッコを含む文字列を取得できる)。

実際にAngularJS 1.5.8のソースコードを調べてみると少しだけ異なるようで、3923行目のextraArgs関数でtoStringの結果を分解しているようですが、replaceの中身の正規表現は『/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg』となっています。カッコが多いのはともかく、この時点ではホワイトスペースを省いていません。さらに、matchも『/^([^\(]+?)=>/』と『/^[^\(]*\(\s*([^\)]*)\)/m』の二種類になっています(前者はES2015の対応っぽいですね)。変数名に)は使えないのだから、『[^\)]*』なんてしなくていいような気はするんですが、念のためそうしているのかもしれません。

なお、実際にextraArgs関数を呼んでいる箇所は3955行目のannotate関数の中のようです。この下のforEach関数(※:念のため言っておくと、この関数はAngularJSで定義している関数です)内のreplaceでホワイトスペースを削除しているようです。replaceの第一引数の正規表現は『/^\s*(_?)(\S+?)\1\s*$/』となっています。どうやら、引数の前後にアンダースコア(_)がついてもいいようですね(ただし、実際に試しとぁけではないので本当にそうなのかは分かりません)。

とりあえず、引数名を取得する方法はわかったので、自分でも実際に試してみることにしました。そこで書いてみたのが以下のようなコード。

第二引数に指定した関数の引数名を取得して、その引数名に対応する第一引数のオブジェクトの値を引数に含めて実行するという関数です。この関数を書くのにあたってこまったのが、どうすれば関数の引数の数を動的に変更できるのかということ。調べてみたら、Functionオブジェクトのapplyメソッドを使えば、配列内の値が引数になって実行されるということが分かって、なんとかできました(参考:Function.prototype.apply() – JavaScript | MDN)。applyの第一引数のthisには特に意味がありません。意味がないのでむしろ、nullとかでもしておくべきだったかもしれません。

というわけでさらにこの関数を実行するサンプルを作成してみました。
サンプル:関数から引数名を取得するサンプル

コードは下記のようになっています。

もう少し実用的なサンプルを作成できればよかったのですが、思いつきませんでした。

それにしても、あらためてJavaScriptは自由度の高いプログラミング言語だと思いしらされました。自由度が高すぎて、AngularJSみたいに、パット見わけがわからないライブラリまでできてしまうという。まあ、AngularJSのせいにするのもなんなので、もう少しAngularJSを勉強しようと思います。

右矢印キーを押すと左にカーソルがうつる不可思議なテキストボックスについて

[`evernote` not found]
[`livedoor` not found]
[`yahoo` not found]

前の会社での話。

実は、前の会社を辞めてからも9月は特別に週一回行っていまして、自分がある程度関わったプログラムのバグ修正なんかをしていました(ほとんど、自分が関わったところではないところに時間を使いましたが)。

そんな中、昨年退職した上司が作成したあるウェブページのテキストボックスが変な挙動をしていました。

そのテキストボックスには、数値が入っていて、右寄せになっていました。その時は特別深く考えたわけではないのですが、CSSのtext-alignでrightを指定しているんだろうなとは思いました。

それはいいのですが、どうも入力するとおかしなことになります。

IEだと右矢印キーを押すと左にカーソルが移動し、左矢印キーを押すと右にカーソルが移動します。よくわからないけど、IEのバグかなと思って試しにChromeで見てみると、なんとChromeでは一番右にカーソルをうつして入力すると左側に文字が追加され、逆に一番左にカーソルをうつして入力すると右側に文字が追加されます。

初めての減少に思わずポカンとしてしまったのですが、多分、keydownでイベントを発生させていて何かやらかしているのだろうと思い調べてみました。すると、予想通り、数値のみしか入力させない記述がしてありました(参考:HTMLのテキストボックスを、できるかぎり半角数値のキー入力のみにする | while(isプログラマ))。

というわけでいろいろ変更してみたのですが、全く変化がありません。そして、試しにkeydownのイベントを無くしてみましたが、全く変化がありませんでした。正直、わけが分かりませんでした。

とりあえず、一から考え直そうと思って開発者ツールで対象のテキストボックスを選択し、右側のサイドバーを見たところ、あると思ったものが無いことに気づきました。そう、『text-align:right』の記述がありませんでした。スクロールバーを上から下まで移動させて見てみたのですが、その記述がみあたりません。「右寄せになってるのになぜ?」もはやパニックに近い状態の自分でしたが、ふと見られない記述がありました。

『direction:rtl』

なんじゃこりゃ? と思って調べてみると、文章の記述方向の設定をしている記述ということが分かりました(参考:direction – CSS | MDN)。rtl は右から左……。まさか……。

この記述を無くしたら、カーソルが想定通りに動きました(もちろん、これだと左寄せになってしまうので、『text-align:right』を追加しておきました)。

とりあえず、同じようなテキストボックスを下記においておきます。valueの値は12345としています。こうすれば、『54321』と表示されるような気がしなくもないんですが、『12345』と表示されています。

というわけで、もし、矢印キーを押したら逆方向にカーソルが移動する場合は、この記述がないか疑ってください。そんなこと、滅多にないと思いますが……。

なお、「テキストボックスに『direction:rtl』の指定があればカーソルの動きがおかしくなる。ということを言いたいだけだろうに、前置きが長すぎ! そんなんだからブログ続かないんだろ!」なんてツッコミは受け付けておりません。むしろ、こう書いたことがセルフツッコミです。