再入門JavaScript: 数値 (Number)

JavaScript の Number オブジェクトは、実数をあらわす。整数を表すクラスと別れていない。

新しい JavaScript (ECMAScript 2020) では BigInt がプリミティブとして導入されたが, Number を置き換えるものではない。

コンストラクタ・リテラル

数値 (リテラル) は 123, 123.4 または 1.23e-3 [浮動小数点表示] と記述する。

コンストラクタ Number(value) に文字列を与えてもよい. parseFloat(str), parseInt(str, [radix]) でも数値オブジェクトを生成できる。

new Number(...) してはならない。エラーにもならないが正常に動かない。

特別な値 NaN, ±Infinity も数値。JavaScript は変数に型がないので,「数値型だが数値ではない」を表すのに、ナル値と異なる値を使わざるをえない。

Number()String() の相互変換

数値や文字列に変換するとどうなるか。

Number() String() parseInt()
undefined NaN "undefined" NaN
null 0 "null" NaN Number(null) は NaN になりそうだが、そうではない。
false 0 "false" NaN
true 1 "true" NaN
"true" NaN "true" NaN
NaN NaN "NaN" NaN
Infinity Infinity "Infinity" NaN
-Infinity -Infinity "-Infinity" NaN
"" 0 "" NaN
"3x" NaN "3x" 3 parseInt() は parse できるところまでで打ち切る
-1.5 -1.5 "-1.5" -1 parseInt() は 0に向かって丸める.
0 0 "0" 0
1.5 1.5 "1.5" 1
[] 0 "" NaN
{} NaN "[object Object]" NaN

parseInt() は, 数値と「数値で始まる文字列」でないものはすべてNaN に変換。その割に文字列の途中までは parse する。

Number() は数値に型変換しようとする。Number("Infinity")Infinity になる。それ以外、数値でない文字列は全部 NaN になる。 Number(Symbol('foo'))TypeError 例外を発生。シンボルからの型変換はできない。

Number({})NaN になる一方で, [] が 0になる。"" と同じノリか。

実際のコーディングでは, Number() で変換したうえで、NaN かどうかで弾くのが現実的、か。

基数変換

parseInt(str, [radix])Number#toString([radix]) を使えばよい。

parseInt('ffff', 16) #=> 65535
Number('65500.456').toString(16)  #=> "ffdc.74bc6a7ef8"

リテラルでは, 0b を頭に付ければ2進数, 0o なら8進数, 0x なら16進数になる。

値の範囲

JavaScript 仕様により, IEEE 754 倍精度64bitフォーマットと決まっている。10進での有効桁数は15桁。次の定数が決まっている。

Number.EPSILON
2つの数値の差の最小. 2.220446049250313e-16
Number.MAX_SAFE_INTEGER
整数として表現できる最大値. 9007199254740991 (= 253 - 1). 263 よりも小さいことに注意。
Number.MAX_VALUE
表現可能な最大値. 1.7976931348623157e+308

演算

ビット演算子の挙動に注意を要する。JavaScript の数値は実数だが、ビット演算子は整数しか受け付けない。暗黙に丸められる。何だって!

~ bitwiseNOT
符号付き32bit整数に丸められる。
(~parseInt('fffffff', 16)).toString(16)  #=> "-10000000"
% 剰余
実数で計算されることに注意。
456.12 % 123.45  #=> 85.77
<<, >>, >>>
ビットシフト.
123.45 << 3.2   #=> 984.  123 << 3 と同じ
&, ^, |
ビット論理積、排他的論理和 (xor), 論理和。整数に丸められる。これを利用したトリックが時々使われる。(しかし読みにく)
x = 15.2
x / 5 | 0  #=> 3.  ビット論理和のほうが除算より優先順位が低いのも活用. 

0除算

0除算はエラーにならない。NaNが顔を出す

-3/0  #=> -Infinity
+3/0  #=> Infinity
0/0   #=> NaN

NaN の神秘

NaN== であれ === であれ、自分自身との比較も false になる。null のほうは、自分自身との比較は true になるのに。

そのため isNaN() 関数がある。

実際には、例えば1以上の整数が欲しいなら > 0 するだけで NaN も弾ける。

グローバルの isNaN() 関数と Number.isNaN() があり、挙動が異なる。グローバルのほうは互換性のためにある。常に Number.isNaN() を使うこと。

グローバルの isNaN()
Number オブジェクトに型変換する。その結果が NaN であれば true を返す。ということは, undefined, 数値に変換できない文字列は true. {} も true. 問題は, null を与えると false になってしまう。Number(null) が 0 になる問題がここで現れる。
isNaN(true) #=> false
isNaN(false) #=> false.
Number.isNaN(obj)
obj の型が Number ではない場合, false. Number だった場合, objNaN の場合, true.

そうすると, undefined, 任意の文字列 (数値に変換できるものであっても) は false になる。

Number() で数値に整えた上で、もし必要であれば Number.isNaN() を呼び出せばいい。