最新の JavaScript では、2つの数値型があります:
-
JavaScript での通常の数値は “倍精度” として知られる64ビットフォーマットIEEE-754 で格納されます。ほとんどの場合で使用する数値はこちらであり、本チャプターでもこちらについて説明します。
-
BigInt は任意の長さの整数を表現するためのものです。通常の数値では
253を超えることや、-253よりも小さい値を扱うことができないため、場合によって必要になります。BigInt は限られた特別な領域で使用されます。BigInt は BigInt で取り上げます。
したがって、このチャプターでは、通常の数値について説明します。では、知識を広げていきましょう。
数値を書く多くの方法
10億を書くことを想像してください。明白な方法は次の通りです:
let billion = 1000000000;
区切りとしてアンダースコア _ を使用することも可能です:
let billion = 1_000_000_000;
ここで、アンダースコア _ は “糖衣構文” の役割をはたし、数値をより読みやすくします。JavaScriptエンジンは単に数字の間にある _ を無視するので、上の 10億とまったく同じです。
ただし、実際には間違えやすさから、通常は何度もゼロを書くのを避けます。また我々は怠け者です。10億を "1bn" としたり、73億を "7.3bn" と書いたりします。大きな数値を表現する場合には大抵これが当てはまります。
JavaScriptでは、数値に文字 "e" を追加し、ゼロの数を指定することで数値を短くします。:
let billion = 1e9; // 10 億, 文字通り: 1 と 9 個のゼロ
alert( 7.3e9 ); // 73 億 (7,300,000,000)
つまり、"e" は 1 と与えられたゼロの数をかけ合わせます。
1e3 = 1 * 1000
1.23e6 = 1.23 * 1000000
今度は非常に小さい数値を書いてみましょう。1マイクロ秒(100万分の1秒):
let ms = 0.000001;
先程書いたように、"e" が役立ちます。明示的にゼロを書くのを避けたい場合、このように書くことができます:
let ms = 1e-6; // 1 から左にゼロを6つ
0.000001 の中のゼロの数は6つです。なので 1e-6 になります。
つまり、"e" の後の負値は 1 と与えられたゼロの数で数値を割ったものになります。
// -3 divides by 1 with 3 zeroes
1e-3 = 1 / 1000 (=0.001)
// -6 divides by 1 with 6 zeroes
1.23e-6 = 1.23 / 1000000 (=0.00000123)
16進数、2進数、8進数(Hex, binary and octal numbers)
16進数 の数値は色、エンコード文字やその他多くのものを表現する文字としてJavaScriptで広く使われています。もちろん、それらを短く書く方法があります: 0x とそれに続いて数値を書きます。
例えば:
alert( 0xff ); // 255
alert( 0xFF ); // 255 (同じです。文字の大小は関係ありません)
2進数と8進数はあまり使われませんが、0b, 0o のプレフィックスでサポートされています:
let a = 0b11111111; // 255 の2進数表記
let b = 0o377; // 255 の8進数表記
alert( a == b ); // true, 両方とも同じ数値(255)
このようなサポートを持つ数字体系は3つしかありません。他の数値体系では、parseInt 関数を使う必要があります(このチャプターで後ほど学びます)。
toString(base)
メソッド num.toString(base) は与えられた base の記数法で num の文字列表現を返します。
例:
let num = 255;
alert( num.toString(16) ); // ff
alert( num.toString(2) ); // 11111111
base は 2 から 36 までの値を取ります。デフォルトは 10 です。
一般的なユースケースは次の通りです:
-
base=16 は 16進数の色や文字エンコードなどに利用され、数字は
0..9またはA..Fになります。 -
base=2 は主にビット単位の操作をデバッグするためのものです。数字は
0か1です。 -
base=36 は最大値です。数字は
0..9またはA..Zです。全てのアルファベットが数値を表現するために使われます。36が役立つケースは、例えばURLを短くするために、長い数値の識別子を何か短いものに変える必要がある場合です。基数36を使うことでシンプルに表現できます。alert( 123456..toString(36) ); // 2n9c
123456..toString(36) の2つのドットはタイプミスではないことに注意してください。上の例の toString のように、数値に対して直接メソッド呼び出しをしたいとき、その後に2つのドット .. を置く必要があります。
もし1つのドットを置いた場合 123456.toString(36)、エラーになるでしょう。なぜならJavaScript構文は最初のドットの後を小数部分と考えるためです。そして、もう1つドットを置くと、JavaScriptは小数部分が空であることを知り、メソッドと判断します。
また、このように書くこともできます (123456).toString(36).
丸め
数値処理で最も使う操作の1つが丸めです。
丸めにはいくつかの組み込みの関数があります:
Math.floor- 切り捨て:
3.1は3になり,-1.1は-2になります。 Math.ceil- 切り上げ:
3.1は4になり,-1.1は-1になります。 Math.round- 四捨五入して最も近い整数にする:
3.1は3になり,3.6は4になります。-1.1は-1です。 Math.trunc(Internet Explorer では未サポート)- 丸めを使わずに、小数点以下を削除:
3.1は3になり,-1.1は-1です。
これらの違いのサマリをテーブルにします:
Math.floor |
Math.ceil |
Math.round |
Math.trunc |
|
|---|---|---|---|---|
3.1 |
3 |
4 |
3 |
3 |
3.6 |
3 |
4 |
4 |
3 |
-1.1 |
-2 |
-1 |
-1 |
-1 |
-1.6 |
-2 |
-1 |
-2 |
-1 |
これらの関数は、数値の小数点の扱い方の全ての可能性をカバーしています。しかし、小数の後の数値を n-th(n桁) に丸めたいときはどうすればよいでしょうか。
例えば、1.2345 という数値があり、1.23 のみを取り出すような、2桁に丸めたい場合です。
それをするために2つの方法があります:
-
乗除算
例えば、小数第2位で数値を丸めるために、 数値を
100で乗算し、丸め関数を呼び出した後、それを除算します。let num = 1.23456; alert( Math.floor(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23 -
メソッド toFixed(n) は点の後の数字を “n” 桁に丸め、結果の文字列表現を返します。
let num = 12.34; alert( num.toFixed(1) ); // "12.3"これは、
Math.roundと似たように、最も近い値に丸めます:let num = 12.36; alert( num.toFixed(1) ); // "12.4"toFixedの結果は文字列であることに注意してください。もし小数部分が指定桁より短い場合、末尾にゼロが挿入されます。:let num = 12.34; alert( num.toFixed(5) ); // "12.34000", 正確に5桁になるよう 0 が追加されます単項プラス、または
Number()呼び出しを使うことで、数値に変換することができます:+num.toFixed(5)
精密でない計算
内部的には、数値は 64bit フォーマットIEEE-754で表現されるため、正確に 64bit で数値を格納できます。: そのうち 52bit が数字の格納のために使われ、11bit が 小数点の位置で(整数値のときゼロです。)、 1bit は符号です。
もし、数値が長すぎる場合、64bitの記憶域をオーバーフローし、無限大になる可能性があります:
alert( 1e500 ); // Infinity
少しはっきりしないかもしれませんが、よく起こるのは精度の低下です。
この(偽となる)テストを考えてみましょう:
alert( 0.1 + 0.2 == 0.3 ); // false
上記の通り、もし 0.1 と 0.2 の合計が 0.3 かどうかをチェックした場合、false になります。
奇妙です! 0.3 でないのなら、何なのでしょう?
alert( 0.1 + 0.2 ); // 0.30000000000000004
なんと! ここには誤った比較よりも多くの驚きがあります。もしもe-ショッピングのサイトを作っており、訪問者が $0.10 と $0.20 の商品をカートに入れたと想像してください。注文の総額は $0.30000000000000004 になります。誰もがそれに驚くでしょう。
しかし、なぜこのようなことが起こるのでしょうか?
数値はバイナリ形式で、1と0の並びでメモリ上に格納されます。しかし10進数でシンプルに見える 0.1、0.2 のような小数は、バイナリ形式では終わることのない小数です。
言い換えると、0.1 とは何でしょう?それは 1 を 10 で割った 1/10 です。10進数では、このような数値は簡単に表現できます。 それと 1/3 を比較してみてください。これは無限の小数 0.33333(3) になります。
従って、10 の累乗による除算は 10進数では上手く動作することが保証されますが、3 による除算は保証されていません。同じ理由で、2進数では、2 の累乗による除算は動作することが保証されていますが、 1/10 は無限の2進数の小数になります。
2進数を使って、 正確な 0.1 または 正確な 0.2 を格納する方法はありません。ちょうど、10進数で 1/3 を小数で正確に表現できないように。
数値形式 IEEE-754 は、可能な限り近い数値に丸めてこれを解決します。 これらの丸めルールでは、通常 “小さな精度損失” は見ることができないので、数値は 0.3 と表示されます。 しかし、損失は依然として存在することに注意してください。
これは次のようにして見ることができます:
alert( 0.1.toFixed(20) ); // 0.10000000000000000555
そして、2つの数値の合計をしたとき、それらの “精度損失” は加算されます。
そういうわけで、 0.1 + 0.2 は正確な 0.3 ではありません。
同じ問題は多くの他のプログラミング言語で存在します。
PHP, Java, C, Perl, Ruby は全く同じ結果を返します。なぜならそれらは同じ数値形式に基づいているためです。
この問題を回避できるのでしょうか?もちろん、幾つかの方法があります。最も信頼できる方法は、toFixed(n) メソッドにより結果を丸めることです:
let sum = 0.1 + 0.2;
alert( sum.toFixed(2) ); // 0.30
toFixed は常に文字列を返すことに注意してください。それは小数点の後2桁となることを保証します。実際、e-ショッピングを持っていて $0.30 を表示するときに便利です。それ以外のケースでは、数値に変換するために単項プラスを使うことができます。
let sum = 0.1 + 0.2;
alert( +sum.toFixed(2) ); // 0.3
一時的に数値を 100 (またはより大きな数値)で乗算し、整数に変換し、計算を行ってから除算することもできます。その後、整数で計算しようとすると、エラーはいくらか減少しますが、依然として除算ではまだ異常です。:
alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3
alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001
そのため、乗算/除算によるアプローチはエラーは減らせますが、完全に除去することはできません。
もしもショップを扱っている場合、最も抜本的な解決策はセントで全ての価格を格納し、全く小数を使わないことです。しかし、30% のディスカウントを適用するとどうなるでしょうか?実際には、完全に回避することはほぼ不可能なので、上記の解決法はこの落とし穴を回避するのに役立ちます。
これを試してみてください:
// Hello! 自己増加する数値です!
alert( 9999999999999999 ); // shows 10000000000000000
これも同じ問題によるものです: 精度の損失です。数値は64bitであり、そのうちの52bitが数値を格納するのに使えますが、十分ではありません。従って、最下位の数字は消えます。
JavaScriptはこのようなイベントではエラーをトリガーしません。数値を目的のフォーマットに合わせるよう最善を尽くしますが、このフォーマットは残念ながら十分大きいものではありません。
数値の内部表現のもう一つの面白い結果は、2つのゼロ、すなわち 0 と -0 の存在です。
記号は1ビットで表現されるため、すべての数字は、0を含めて正または負になります。
ほとんどの場合、区別は気になりません。なぜなら演算子はそれらを同じものとして扱うためです。
テスト: isFinite と isNaN
2つの特別な数値を覚えていますか?
Infinite(と-Infinite) は何よりも大きい(小さい)特別な数値です。NaNはエラーを表現します。
これらは number 型に属しますが、 “通常の” 数値ではないため、それらをチェックするための特別な関数があります:
-
isNaN(value)はその引数を数値に変換し、NaNであるかをテストします:alert( isNaN(NaN) ); // true alert( isNaN("str") ); // trueしかし、この関数は必要なのでしょうか?単に比較
=== NaNは使えないのでしょうか?答えは No です。NaNはそれ自身を含めて何とも等しくなく、ユニークです:alert( NaN === NaN ); // false -
isFinite(value)は引数を数値に変換し、それがNaN/Infinity/-Infinityではなく通常の数値であれば、trueを返します。:alert( isFinite("15") ); // true alert( isFinite("str") ); // false, 特別な値: NaN なので alert( isFinite(Infinity) ); // false, 特別な値: Infinity なので
時々、isFinite は文字列値が通常の数値かどうかをチェックするのに使われます:
let num = +prompt("Enter a number", '');
// Infinity を入力しなければ true です -- Infinity は数値ではありません
alert( isFinite(num) );
空、またはスペースのみの文字列は、isFinite を含めた全ての数値関数で 0 として扱われる点に注意してください。
Object.is を使った比較値を === のように比較する特別な組み込みメソッド Object.is があります。これは2つの特殊なケースではより信頼できます:
NaNでうまく動作します:Object.is(NaN, NaN) === true、これは良いことです。- 値
0と-0は異なります:Object.is(0, -0) === false、重要ではありませんが、技術的にはそれらは異なる値です。
上記以外の全てのケースでは、Object.is(a, b) は a === b と同じです。
この比較の方法は JavaScriptの仕様でしばしば使われます。内部のアルゴリズムが2つの値が正確に同じかを比較する必要があるとき、Object.is (内部的にはSameValueと呼ばれます)を使います。
parseInt と parseFloat
プラス + または Number() を使った数値変換は厳密です。もし値が厳密な数値でない場合は失敗します:
alert( +"100px" ); // NaN
唯一の例外は文字列の最初、または最後のスペースです。それらは無視されます。
しかし、実際にはCSS で "100px" や "12pt" のような単位を持つ値を持つことが頻繁にあります。また、多くの国では、通貨記号が金額の後にあるので、"19€" と言った値があり、その中から数値を抽出したいと考えています。
そのために parseInt と parseFloat があります。
それらはできるだけ文字列から数値を “読み込み” ます。エラーが起きると、収集された数値が返されます。関数 parseInt は整数を返し、一方で、parseFloat は浮動小数を返します:
alert( parseInt('100px') ); // 100
alert( parseFloat('12.5em') ); // 12.5
alert( parseInt('12.3') ); // 12, 整数部のみ返却されます
alert( parseFloat('12.3.4') ); // 12.3, 2つ目の点で読むのをやめます
parseInt/parseFloat が NaN を返す状況があります。それは数値が読み込めなかったときに発生します:
alert( parseInt('a123') ); // NaN, 最初の文字で処理が止まります
parseInt(str, radix) の2つ目の引数parseInt() 関数は任意の2つ目のパラメータを持ちます。それは数値システムの基数を指定するので、parseInt もまた16進数、2進数などの文字列をパースすることが出来ます:
alert( parseInt('0xff', 16) ); // 255
alert( parseInt('ff', 16) ); // 255, 0x がなくても動作します
alert( parseInt('2n9c', 36) ); // 123456
他の数学的関数
JavaScript は数学関数と定数の小さなライブラリを含む組み込みのMath オブジェクトを持っています。
いくつか例です:
Math.random()-
0 から 1まで (1は含みません)のランダムな数値を返します
alert( Math.random() ); // 0.1234567894322 alert( Math.random() ); // 0.5435252343232 alert( Math.random() ); // ... (any random numbers) Math.max(a, b, c...)/Math.min(a, b, c...)-
任意の数の引数から最大/最小を返します
alert( Math.max(3, 5, -10, 0, 1) ); // 5 alert( Math.min(1, 2) ); // 1 Math.pow(n, power)-
nを与えられた数だけ累乗しますalert( Math.pow(2, 10) ); // 2 in power 10 = 1024
docs for the Math にある三角法など、Math オブジェクトにはより多くの関数や定数があります。
サマリ
大きな数値を書く場合:
- 数値へ加えるゼロの数と一緒に
"e"を付け加えます。このように:123e6は123と 6つのゼロです。 "e"の後の負の値は、1と与えられたゼロの数で数値を除算します。
異なる数値体系の場合:
- 16進数(
0x)、8進数(0o)や2進数(0b) で直接数値を書くことが出来ます。 parseInt(str, base)は2 ≤ base ≤ 36の間の任意の数値システムの整数を解析します。num.toString(base)は数値を、与えられたbaseを基数とした数値システムの文字列に変換します。
12pt や 100px のような値を数値に変換する場合:
- “ソフト” 変換として
parseInt/parseFloatを使います。それは文字列から数値を読み込み、エラーが起きる前までに読めた値を返します。
分数の場合:
Math.floor,Math.ceil,Math.trunc,Math.roundまたはnum.toFixed(precision)を使って丸めます。- 分数を扱う際に精度の低下があることを覚えておいてください。
より多くの算術関数:
- 必要なとき、Math を見てください。このライブラリはとても小さいですが、基本で必要なものはカバーしています。
コメント
<code>タグを使ってください。複数行の場合は<pre>を、10行を超える場合にはサンドボックスを使ってください(plnkr, JSBin, codepen…)。