DanLevy.net

クイズ:JavaScript、ちゃんと数えられますか?

` parseInt と parseFloat、区別つきますか? `

数字だけなら簡単……ですよね? 🤓

parseInt(" 123456.00")

parseInt 関数は先頭の空白を無視し、先頭の数字の列を整数としてパースします。ここでは小数点の位置で処理が停止するため、123456 のみが返されます。

parseInt("123,456.00")

原則として、parseInt は数値以外の文字に遭遇すると解析を停止します。このケースではカンマがそのトリガーとなるため、結果として 123 のみが返されます。

0.1 + 0.2 === 0.3

浮動小数点数の精度エラーにより、0.1 + 0.2 は厳密には 0.3 と等しくなりません。メモリ内での浮動小数点数の格納方式により、実際の計算結果は 0.30000000000000004 になります。浮動小数点演算を規定する IEEE 754 標準が原因で、特定の数を完全に正確に表現できないのです。これはあらゆるプログラミング言語に共通する課題です。無限循環小数にぶつかった時点で、どの言語を使おうとコンピュータは無限に続く桁の計算をどこかで打ち切らざるを得ません。

Python や Java などの言語にはこの問題を回避するための Decimal や BigDecimal が標準で備わっていますが、JavaScript には組み込まれていません。こうしたケースを扱うには、big.js や decimal.js といったライブラリの導入が推奨されます。

(注:分数や虚数などをより高次の論理レベルで扱い、リテラル式をそのまま保持するよう設計された言語も存在します。しかし、ハードウェアレベルでは依然として同じ浮動小数点数の精度問題と向き合わなければなりません。)

Number.MAX_VALUE * 2

Number.MAX_VALUEはJavaScriptで表現可能な最大の標準の数値です。この限界を超えるとすぐにオーバーフローし、実質的に意味のない値が返されます。これに2を掛けると、結果はInfinityになります。

まあ、JavaScriptって時々こうなるんですよね。

このコードを実行するとどうなる?

5..toFixed(2)

.toFixed(2) は 5 を小数点以下2桁の文字列として返すため、結果は "5.00" になります。

ドットが2つ続く記法(5..toFixed(2))は、数値リテラルのオブジェクトモデルにアクセスするためのちょっとした「トリック」です。

parseInt("42") === parseFloat("42")

JavaScriptにおいて、parseInt と parseFloat はどちらも文字列 "42" を数値 42 として解釈します。そのため、parseInt("42") === parseFloat("42") の比較結果は true になります。parseInt は最初の非数字文字で解析を停止しますが、parseFloat は浮動小数点数の一部ではない文字に遭遇するまで解析を続けます。ただし、"42" には小数点やその他の非数値文字が含まれていないため、両関数は同じ値を返します。

BigInt("42") === parseInt("42")

BigInt は number とは異なるプリミティブ型です。そのため、parseInt("42") が返す通常の数値は、BigInt("42") と厳密等価(===)にはなりません。値を正しく比較するには、両方を同じ型に揃える必要があります。例えば BigInt(parseInt("42")) === BigInt("42") のように明示的に変換してください。

このコードの実行結果はどれだ?

parseInt("0x2A") === parseInt("2a", 16)

先頭が 0x で始まる文字列は、自動的に16進数(基数 16)として扱われます。 つまり、基数に16を明示的に渡すのと同じ動作になります。したがって、parseInt("0x2A") は parseInt("2a", 16) と完全に同等です。(大文字・小文字は区別されません。)

さて、これはどうなる?

parseInt('0xFF', 16)

parseInt に16進数(16)の基数を指定すると、"FF" が10進数の 255 に変換されます。CSSのRGB/16進数カラーコードで見たことがある人も多いはずです。

[24, 'One', 42].map(parseInt)

map はコールバックに値とインデックスを渡すため、parseInt の第2引数である基数にインデックスが入ります。24 は基数0(自動判定)で24、One は無効な基数1でNaN、42 は基数2で有効な数字から始まらないためNaNとなり、結果は [24, NaN, NaN] です。

文字列全体を数値変換するには .map(Number)、10進数の整数部分を先頭から読み取るには .map(x => parseInt(x, 10)) を使います。両者のパース規則は異なるので、意図に合わせて選びます。

[24, 'Twenty1', 0o42].map(Number)

Number は parseInt よりも厳密に値を数値型に変換します。ここでは、'Twenty1' は NaN になり、0o42 は8進数リテラルとして認識されて 34 に変換されます。

このコードの実行結果は?

console.log(parseInt(null), Number(null))

parseInt は入力を文字列に強制変換するため、null は "null" になります。"null" には有効な10進数の文字(通常の数字)が含まれていないため、NaN が返されます。

Number(null) は 0 を返します。JSは開発者を油断させないのが好きなんですよね。 なぜそうなるのか?興味があれば、もっと深く掘り下げるかもしれません。

この呪文の実行結果は?

parseInt(null, 36)

parseInt は常に引数を文字列に強制変換するため、null は文字列 "null" になります。

36進数(hexatrigesimal、覚えてますか?)では、文字列 "null" は 1112745 を表します。

36進数において、nulk、null、nulm の連続する値はそれぞれ 1112744、1112745、1112746 です。

比較表

関数parseIntparseFloatNumberBigInt
空白文字を無視✅✅✅✅
.map(FN)❌☑️✅✅
基数(radix)引数をサポート✅❌❌❌
文字列内の2進数・8進数・16進数プレフィックス16進数の 0x のみ。0b/0o は自動判定しない❌✅✅
無効な文字を含む 42 oh no4242NaNSyntaxError

手応えはどう? 🧐

2進数攻めで一息つきたい?
ふっ、覚えておけ:スキルを身につけた 後 に休め!

もっと課題を叩きのめしたいなら、俺のジム へ!💪