uhyohyo.net

JavaScript初級者から中級者になろう

十一章第六回 プリミティブについて2

このページの最終更新日:

前回に続いてプリミティブの話です。前回は文字列について主に解説しました。

今回はまず数値の話です。

数値の種類

数値といっても、数にはいろいろな種類があります。プログラミング言語によっては、数値は整数しか扱えないint型や、小数も扱えるfloat型やdouble型などと区別されている場合もあります。また言語によっては虚数をサポートしているものもあるそうです。

一方、JavaScriptにおける数値は整数も小数も別け隔てなく数値として扱います。次の例を見てください。


var a = 2;
a /= 3;
console.log(a);	// 0.6666666666666666

整数同士の計算の結果が小数になりました。整数と小数を区別する言語では整数同士の計算はかならず整数になることもあり、それとは対称的です。

ただし、JavaScriptにおける数値には、普段我々が数と呼ぶものとは別にいくつか特別な値があります。1つはInfinityです。これはつまり「無限大」です。

数学では、一般には「0で割ってはいけない」ということになっています。しかし、JavaScriptにおいては0で割ってもエラーになりません。その代わりに、0で割った結果はInfinityになります。


console.log(3/0);	// Infinity

つまり、JavaScriptにおいては0で割ると無限大になるものとして、無限大を数値の一種として扱っているということになります。

また、無限大にも正の無限大と負の無限大があります。負の値を0で割った場合には-Infinityとなります。


console.log(-5/0);	// -Infinity

Infinityは、数ではないとはいえ数値の一種として扱われます。typeofで調べると確かに数値であることが分かります。


console.log(typeof (1/0));	// "number"

なお、Infinityはundefinedと同様に変数として存在しているので、Infinityと書けばInfinityを得ることができます。


console.log(Infinity);

また、Number.POSITIVE_INFINITYとしてもInfinityを得ることができます。負の無限大(-Infinity)が入ったNumber.NEGATIVE_INFINITYもあります。

Infinityの扱い

Infinityも数値ですから、四則演算その他の演算ができます。無限大に数値を足したり引いたりしても無限大のままです。


Infinity+100	// Infinity
Infinity-50	// Infinity

また、掛け算割り算も基本的に結果はInfinityのままですが、正の無限大に負の数をかけると負の無限大になります。逆も同様です。


Infinity*0.5	// Infinity
Infinity/(-2)	// -Infinity

ただし、無限大を含む計算というのは常にはうまくいきません。例えば、Infinity+InfinityInfinityとなりますが、Infinity-Infinityはどうなるでしょうか。細かい数学的事情は省きますが、無限大の場合両辺が同じだから結果は0というわけにはいきません。数学的には、このような計算は結果が決まらない(定義できない)と解釈されます。

このように結果が決まらない場合、その値はNaNになります。


console.log(Infinity-Infinity);	// NaN

NaN

NaNは以前も少し説明しましたね。NaNはNot a Numberという名前なのに数値扱いというとても不思議な値でした。もちろん、typeofで確かめると"number"になります。


console.log(typeof Number.NaN); // "number"

数値計算において、計算できないようなものは全部結果がNaNになります。Infinity-Infinityの他にもInfinity/InfinityなどもNaNになります。

また、先ほど何かを0で割るとInfinityと述べましたが、0で0を割った形はNaNになります。

NaNを含む計算の結果が全てNaNとなるのは、九章第七回で解説したとおりです。

さらに、NaNが出てくるのは計算時だけではありません。文字列から数値への変換時に、変換できない文字列はNaNになります。

parseInt・parseFloat

数値への変換、parseIntもNaNを返す可能性があります。parseIntは基礎第四回に何気なく出てきた組み込み関数で、文字列を数値に変換するというものでした。


parseInt("123")	// 123

しかし、文字列を全て数値に変換できるわけではないのは明らかです。そういう場合NaNが返ります。


parseInt("foooooo")	// NaN
parseInt("")	// NaN

ところで、九章第七回ではプリミティブの変換について解説したのを覚えているでしょうか。それによれば、変換したい値をNumber関数に渡せば数値に変換できるはずです。


Number("123")  // 123

では、この方法とparseIntとでは何が違うのでしょうか。実は、主に3つの違いがあります。ひとつは、parseIntは数値の後に文字列が続く場合はそれは無視して数値を返してくれるという点です。


parseInt("123px")	// 123

一方のNumberは、余計なものがついている文字列は全てNaNです。


Number("123px")	// NaN

2つ目は、parseIntのIntというのはInteger(整数)のことですから、文字列を整数として解釈するという点です。


Number("100.5")  // 100.5
parseInt("100.5")  // 100

parseInt("100.5")が100となる理由は、上述の機能により.5の部分が無視されるからです。

3つ目は、parseIntには基数を指定する機能がついているという点です。実はparseIntは第2引数を渡すことができ、それは基数です。基数というのは、その文字列を何進法として解釈するかということです。もちろんデフォルトは10進法です。

例えば、10000は2進法では32を表しますから、


parseInt("10000")	// 10000
parseInt("10000", 2)	// 32

となります。

基数は、2から36まで可能です。10より大きい基数としては16進法が有名ですね。


parseInt("ff",16)	// 255

16進法では、a〜fを10〜15番目の数字として扱います。同様に、gを16,hを17,...としてあてはめていくとzが35になりますから、36進法まで可能なわけです。


parseInt("z",36)	// 35

ついでに、もうひとつparseIntに類似の関数としてparseFloatを紹介しておきます。これはparseIntとは異なり、小数でも正しく解釈してくれます。実は数値リテラル(後述)は全て解釈してくれます。


parseFloat("100.5")	// 100.5

ただし、parseFloatは小数も解釈できるようになった代わりに、10進数しか対応しません。そのため第2引数はありません。このため、文字列を数値に変換する場合、Numberによる変換との違いは後ろに続く文字列が無視されるという点のみです。

ただし、Numberは「任意のものを数値に変換する」のに対してparseFloat(やparseInt)は「文字列を数値に変換する」ので、後者に文字列以外のものを渡すとまず文字列に変換してから数値に変換されます。これにより両者が異なる結果となる場合もあります。

数値の比較

つまるところ、JavaScriptの数値は普通の数(整数・小数)と、±Infinity及びNaNからなります。

上で数値の計算について紹介しましたが、数値の比較(==とか不等号とか)はどうでしょうか。

Infinityについては直感通りの結果です。何と比較しても大きいです。


100<Intinify	// true

-Infinityが何よりも小さいのも同様です。

ただし、NaNについては注意が必要です。前に述べたようにNaNが入ると全てfalseになります。


NaN<3	// false
NaN===3	// false
NaN>3	// false

isFinite

さてやはり、普通の数値と、InfinityやNaNは一線を画したところがあります。そこで、これらを区別する便利な方法を解説します。

まずは関数isFiniteです。Finiteとは、Infinite(無限の)と逆で、有限ということです。この関数は数値の引数を一つとって、InfinityやNaNならfalseを返し、普通の数値ならtrueを返します。これで判別が可能です。

数値以外を渡したときは数値に変換してから判定します。

isNaN

もう一つ紹介するのがisNaNです。読んで字の如しという感じですが、引数に渡された数値がNaNならばtrue、そうでなければ(Infinityなども含めて)falseになります。isFiniteとは逆の関係なので注意しましょう。


isNaN(3)	// false
isNaN(Infinity)	// false
isNaN(NaN)	// true

数値リテラル

次はリテラルの話です。リテラルとは、JavaScriptのスクリプト上で何らかの値として解釈される文字列のことです。

例えば、JavaScriptで文字列を表すには"foobar"'12345'のように""(ダブルクォーテーション)か''(シングルクォーテーション)で囲います。つまり、文字列を表現するにはこのように書かないといけません。

ですから、この""や''で囲まれたものを文字列リテラルといいます。他に、簡単なのが真偽値リテラルです。真偽値を表すにはtruefalseと書きます。この2つが真偽値リテラルです。

他にはnullもリテラルです。一方、実はundefinedはリテラルではなく変数ということになっています。スクリプト上にundefinedと書いた時はそれはundefinedのリテラルではなく、undefinedという値が入った変数が予め用意されているだけなのです。しかし、writable属性(十一章第四回参照)がfalseになっているので書き換えられません。

またリテラルはプリミティブばかりに用意してあるわけではなく、オブジェクトリテラル配列リテラルもあります。これらは既にお馴染みだと思います。


{
  foo:"bar",
  baz:3,
}
[0, 1, 2, 3]

のようなものがそれぞれオブジェクトリテラルや配列リテラルです。他に正規表現リテラルというのもありますね。

そして最後に残ったのが問題の数値リテラルです。実は数値リテラルというのはいろいろ種類が豊富なのです。それを紹介します。

最も一般的に使うのは、ただ数字を並べただけの次のようなリテラルだと思います。


0
123
15000

また、小数点を使ったものも数値リテラルの一種です。


0.5
3.00
3.14

ただし注意してほしいのは、次のようなのは数値リテラルではありません。


-3
-0.5

というのも、マイナスがついたのはひとまとまりの数値リテラルではなく、単項演算子の-(マイナス)の後に数値リテラルがくっついた値であるということです。その証拠に、

console.log(- 100);

のようにマイナスを離しても動作します。単項演算子-は、簡単にいうと数値の符号を逆にして返す演算子です。ですから、上のコードの場合、まず正の数100があって、-によって処理されて負の数-100が返されてconsole.logに渡されているのです。

また、この単項演算子-は、演算子ですから数値リテラル以外にも付けられます。


var num=10;
console.log(-num);	// -10

注意してほしいのは、これはJavaScriptの数値は負の数を扱えないということではなく、負の数を直接表すリテラルが無いということです。

では、ここから見慣れない形の数値リテラルを紹介していきます。

実は、小数点だけ書いてその先を省略する形の数値リテラルも許されています。


3.	// 3.0と同じ(つまり3と同じ)
0.	// 0と同じ

ただ、3.と書くくらいなら3と書けばいいはずなので、JavaScriptで3.のように書く意味はないでしょう。(この記法は整数と小数が区別される言語において小数を少ない文字数で書くためのもので、他の言語との統一性のためにJavaScriptでも使えるようになっているものです。)

さらに、数値リテラルには指数部をつけることができます。指数部は、eのあとに整数を付けます。例えば、1e4という形です。1e4というのは、「1×104」を意味します。つまり指数というのは10の指数です。

ですから、1e4というのは、1を104倍した数、つまり10000になります。他の例も見てみましょう。


1e4	// 10000
2.5e3	// 2500
5.E2	// 500

最後の例のように、eは大文字でも構いません。

また、指数(eのあとの数字)は-(マイナス)を付けて負の数にしても構いません。例えば、


3e-2	// 0.03
1.3e-6	// 0.0000013

途中でマイナスが入っているので計算式のように見えますが、あくまでこれでひとつの数値リテラルです。数値リテラルですから、もちろん次のようにプログラム中で自由に使えます。


console.log(1e5-3.14e+2);	// 99686

また、eのあとに-がついていいなら+もついていいだろうということで、上で出たような3.14e+2のような形も許されます。意味は、+を省略した場合と変わりません。

さらに、16進数のリテラルがあります。数値リテラルを0xまたは0Xで始めて、その後16進数の数字(0〜9,a〜fまたはA〜F)を並べることができます。この記法は整数のみであり、eを使って指数を示す記法は使えません。


0xff	// 255
0xDEADBEEF	// 3735928559

プログラム中では16進数を使うこともわりとあるので、その場合に重宝します。

数値のメソッド

それではいよいよ、数値のメソッドについて見ていきます。

文字列の場合は、一時的にStringオブジェクトが作られてそのメソッドが呼ばれるのでした。同様に、数値の場合にはNumberオブジェクトが作られます。ですから、今から紹介するメソッドは全てNumber.prototypeにあるメソッドです。

toString

toStringというのは九章第七回で出てきましたね。その名の通り、数値を文字列に変換するメソッドです。


var num=10;
console.log(num.toString());	// "10"

以前でてきたtoStringは引数をもたないメソッドでしたが、数値のtoStringは特別で、引数に基数を渡すとその進法で変換してくれます。parseIntと同じく、基数は2から36までです。


var num=10;
console.log(num.toString(2));	// "1010"(2進法で10)

省略した場合はもちろん10進数です。

toLocaleString

またLocaleがついた関数がでました。これは、ユーザーの言語設定に合わせた表記法で数値が文字列に変換されます。

ただ、日本人(や英語圏の人々)は数字は普通に123とかそういう数字(アラビア数字)で書くのが普通ですから、toStringと同じような結果しか出ないでしょう。場合によっては、見る人に合った結果が出るかもしれません。

この関数は人間が見やすい形で数値を表示してくれます。例えば、多くのブラウザでは桁区切りの記号が入ります。


var a=30000;
console.log(a.toLocaleString()); // "30,000"

toLocaleStringはlocaleCompareと同様に第2引数に言語タグ・第3引数にその他のオプションを取ります。これについてはここでは詳しく解説しません。

toFixed

どうしても、数値のメソッドは文字列に加工することくらいしかやることがないので、そういうメソッドが多くなります。toFixedもそのようなメソッドの一つです。

toFixedは、小数点以下の桁数を引数に指定して文字列にしてくれます。小数点以下の桁数が足りない場合は0で埋めます。また、あふれる分は四捨五入して丸められます。


1.234.toFixed(3)	// 1.234
5.1.toFixed(5)	// 5.10000
123456.789.toFixed(2)	// 123456.79

小数点より上の位はそのまま表示されます。負の数に対しても正しく動作します。

また、桁数は0桁(整数に丸める)から20桁まで処理可能なことが保証されています。それ以上の桁数を処理できるかどうかはブラウザ依存で、エラーが発生する場合もあります。

toExponential

Exponentialというのは指数ということです。これは、数値をさっき数値リテラルで紹介したeを使った表記に直してくれます。引数はtoFixedと同じく、小数点以下の桁数です。

考え方は有効数字に近いもので、かならず小数点の前(1の位)に数字が一つくるようになります。


12345.6.toExponential(6)	// 1.234560e+4

これは、12345.6という数字は、有効数字7桁(1の位も含めて)でいうと1.234560×104であることを示した表記ですね。

また、桁数が足りない場合はtoFixedと同様四捨五入してくれます。


0.000045678.toExponential(3)	//4.568e-5

toPrecision

最後に、さらに似たようなのをもう一つ紹介します。Precisionとは有効数字のことです。

これはtoExponentialとよく似たメソッドです。toExponentialでは小数点以下の桁数を引数として指定しましたが、toPrecisionでは小数点より前(1の位)の桁数も含めて考えます。これはより一般的な有効数字の概念に近いですね。

また、指数表示(e)を使わなくてもいけそうな範囲ではeを使わずに表示してくれるという違いもあります。toExponentialとの比較で結果を見てみましょう。


3.0.toExponential(2)	// "3.00e+0"
3.0.toPrecision(3)	// "3.00"

0.05.toExponential(2)	// "5.00e-2"
0.05.toPrecision(3)	// "0.0500"

3e3.toExponential(1)	// "3.0e+3"
3e3.toPrecision(2)	// "3.0e+3"
3e3.toPrecision(4)	// "3000"
3e3.toPrecision(5)	// "3000.0"

0.0000001.toExponential(3)	// "1.000e-7"
0.0000001.toPrecision(4)	// "1.000e-7"

0.000001.toExponential(3)	// "1.000e-6"
0.000001.toPrecision(4)	// "0.00000100"

eを使った表示に切り替わる条件の一つは3番目の例からうかがい知ることができます。

条件は、その有効数字で少なくとも1の位まで全部表示できるなら数字を直接表示して、桁数が足りなくて1の位まで表示できない場合はeを使った表記になります。

また凄く小さい数については4番目に例を示しましたが、eのあとの指数が-7以下になりそうならeを使い、-6以上でおさまりそうなら直接表示します。

ある程度現れそうな数字の範囲が分かっていれば、eが出てこないので違和感ない表示もできるなど、わりと使い勝手のいいメソッドです。

以上で数値のもつメソッドの紹介は終わりです。

数値リテラルとプロパティ表記

上の解説では小数を多く使っていました。ピリオドが2つ並んで多少気持ち悪いかもしれませんが、1つ目のピリオドは小数点(数値リテラルの一部)で、2つ目のピリオドはリテラルとメソッド名をつなぐピリオドです。


3.0.toExponential(2)
^^^
ここが数値リテラル

では、整数ではだめなのでしょうか。


3.toExponential(2)
^

実はこれはエラーになります。なぜかというと、さっき上のほうで0.とか3.とかの「小数点以下を省略する記法」を紹介しました。こちらの文法が優先されているからです。すなわち、次のように解釈されているということです。


3.toExponential(2)
^^
ここがリテラル

するとこれは、3toExponentialというのと同じ意味になり、これは文法的にはおかしいですね。ですからエラーになるのです。

解決策の一つは、()で囲んで分離することです。

(3).toExponential(2)	// "3.00e+0"

もう一つの解決策は、次のような形です。


3..toExponential(2)

ピリオドが2つ並んでますます気持ち悪い形になりました。これの構造は次のようになっています。


3..toExponential(2)
^^

つまり、数値リテラルはピリオドを1つしか含みませんから、2つ目のピリオドはプロパティ参照の.として正しく解釈されるのです。

もちろん他にも、


3.0.toExponential(2)
3e0.toExponential(2)

などの形でもできますが、ピリオドを2つ並べる形が面白いのか、案外使われているようです。

ちなみに3e0のほうはまだピリオドが登場していませんが、文法上eのあとには整数しかこないので、この形でも.が数値リテラルの一部と解釈されることはありません。

Numberのプロパティ

Numberはコンストラクタであり、それ自体は関数です。しかし、Numberはそれ自身がいくつかのプロパティを持っています。

Number.MAX_VALUE, Number.MIN_VALUE

それぞれ「最大値」、「最小値」という意味です。本来実数はどこまでも大きくなることができるので最大値などは無いのですが、コンピュータですから記憶できる量に上限があり、したがってJavaScriptの数値型で扱うことができる数にも限りがあります。もっとも、普段はこれが支障になることはないでしょう。なお、JavaScriptで扱える数値の範囲はIEEE 754倍精度浮動小数点数の範囲と同じです。


Number.MAX_VALUE	// 1.7976931348623157e+308

見てわかるようにMAX_VALUEはとても大きな数です。また、MIN_VALUEは負の数ではなく、正の数として扱える中で最も小さい数になります。つまり、なるべく0に近い数です。


Number.MIN_VALUE	// 5e-324

MIN_VALUEはMAX_VALUEに加えて有効数字が少ない気がしますが、これは非正規化数であるためです。

これはJavaScriptではなく浮動小数点数の一般論ですが、このように極端に大きいまたは小さい数を扱うときは演算が不安定になります。例えば、


var a= Number.MAX_VALUE;
var b=a-1;
console.log(a===b);	// true

bのほうがaより1小さいはずなのに、a===bと言われてしまいます。こうなる理由は、10308という極めて大きい数の世界ではたった1など小さすぎて記憶されないからです。

影響を与えるほど十分大きな数を足してみると、次のようなことが起こります。


console.log(Number.MAX_VALUE + 1.0e300);	// Infinity

限界を超えて大きくなった数はInfinityに変わります。ですから、


console.log(Number.MAX_VALUE + 1.0e300 -1.0e300);	// Infinity

このように同じ数を足して引いても結果が変わるというようなことも起こります。また、最初から大きすぎる数を表現しようとした場合もInfinityになります。


console.log(1e400);	// Infinity

しかし、実際の使用の範囲ではこのような問題はめったに発生しないでしょう。

ちなみに、MIN_VALUEについても同じようなことが起こります。MIN_VALUEはJavaScriptで表現できる数の中で0より大きい中で最も小さい数なので、その間の数は出現しません。


Number.MIN_VALUE-2.47032822e-324	// 5e-324
Number.MIN_VALUE-2.47032823e-324	// 0

真偽値

さて、数値についても終了したので最後に残った真偽値のプリミティブの話に移りたいところですが、真偽値については特筆事項はありません。Boolean.prototypeには特にメソッドはありません。

これでプリミティブの扱い方に関する話は終了です。