JavaScript不完全プログラマーガイド (ES2022版)
本書をサポートしてください: 購入 または 寄付
(広告です。ブロックしないでください)

16 数値



JavaScriptには2種類の数値があります。

この章では数値について説明します。BigIntについては、本書の後半で説明します

16.1 数値は浮動小数点数と整数の両方で使用されます

JavaScriptでは、number型は整数と浮動小数点数の両方で使用されます。

98
123.45

ただし、すべて数値は倍精度浮動小数点数であり、IEEE標準754に従って実装された64ビットの浮動小数点数です。

整数は、単に小数点以下の部分のない浮動小数点数です。

> 98 === 98.0
true

内部的には、多くのJavaScriptエンジンは真の整数を、関連するパフォーマンスとストレージサイズの利点とともに使用できることがよくあります。

16.2 数値リテラル

数値のリテラルを見てみましょう。

16.2.1 整数リテラル

いくつかの整数リテラルにより、さまざまな基数を持つ整数を表現できます。

// Binary (base 2)
assert.equal(0b11, 3); // ES6

// Octal (base 8)
assert.equal(0o10, 8); // ES6

// Decimal (base 10)
assert.equal(35, 35);

// Hexadecimal (base 16)
assert.equal(0xE7, 231);

16.2.2 浮動小数点リテラル

浮動小数点数は、10進数でのみ表現できます。

分数

> 35.0
35

指数: eN は ×10N を意味します。

> 3e2
300
> 3e-2
0.03
> 0.3e2
30

16.2.3 構文上の落とし穴: 整数リテラルのプロパティ

整数リテラルのプロパティにアクセスすると、落とし穴があります。整数リテラルの直後にドットが続く場合、そのドットは小数点として解釈されます。

7.toString(); // syntax error

この落とし穴を回避するには4つの方法があります。

7.0.toString()
(7).toString()
7..toString()
7 .toString()  // space before dot

16.2.4 数値リテラルにおけるアンダースコア (_) のセパレータとしての使用 [ES2021]

長い数値を読みやすくするために桁をグループ化する方法は、長い伝統があります。例えば

ES2021以降、数値リテラルではアンダースコアをセパレータとして使用できます。

const inhabitantsOfLondon = 1_335_000;
const distanceEarthSunInKm = 149_600_000;

他の基数でも、グループ化は重要です。

const fileSystemPermission = 0b111_111_000;
const bytes = 0b1111_10101011_11110000_00001101;
const words = 0xFAB_F00D;

分数と指数にもセパレータを使用できます。

const massOfElectronInKg = 9.109_383_56e-31;
const trillionInShortScale = 1e1_2;
16.2.4.1 セパレータはどこに配置できますか?

セパレータの配置場所は2つの点で制限されています。

これらの制限の背景にあるのは、構文解析を単純に保ち、奇妙なエッジケースを避けることです。

16.2.4.2 セパレータを含む数値の構文解析

数値を構文解析するための次の関数は、セパレータをサポートしていません。

例えば

> Number('123_456')
NaN
> Number.parseInt('123_456')
123

その理由は、数値セパレータはコードのためのものであり、他の種類の入力は異なる方法で処理する必要があるためです。

16.3 算術演算子

16.3.1 二項算術演算子

5 に、JavaScriptの二項算術演算子が一覧表示されています。

表 5: 二項算術演算子。
演算子 名称
n + m 加算 ES1 3 + 4 7
n - m 減算 ES1 9 - 1 8
n * m 乗算 ES1 3 * 2.25 6.75
n / m 除算 ES1 5.625 / 5 1.125
n % m 剰余 ES1 8 % 5 3
-8 % 5 -3
n ** m べき乗 ES2016 4 ** 2 16
16.3.1.1 % は剰余演算子です

% は剰余演算子であり、モジュロ演算子ではありません。その結果は、最初のオペランドの符号を持ちます。

> 5 % 3
2
> -5 % 3
-2

剰余とモジュロの違いの詳細については、2alityのブログ投稿“Remainder operator vs. modulo operator (with JavaScript code)”を参照してください。

16.3.2 単項プラス (+) と単項マイナス (-)

6 は、2つの演算子単項プラス (+) と単項マイナス (-) をまとめたものです。

表 6: 単項プラス (+) と単項マイナス (-) 演算子。
演算子 名称
+n 単項プラス ES1 +(-7) -7
-n 単項マイナス ES1 -(-7) 7

どちらの演算子も、オペランドを数値に変換します。

> +'5'
5
> +'-12'
-12
> -'9'
-9

したがって、単項プラスを使用すると、任意の値を数値に変換できます。

16.3.3 インクリメント (++) とデクリメント (--)

インクリメント演算子++は、プリフィックス版とポストフィックス版があります。どちらのバージョンでも、オペランドに1を加算します。したがって、そのオペランドは変更可能な記憶域場所である必要があります。

デクリメント演算子--は同じように機能しますが、オペランドから1を減算します。次の2つの例は、プリフィックス版とポストフィックス版の違いを説明しています。

7 は、インクリメント演算子とデクリメント演算子をまとめたものです。

表 7: インクリメント演算子とデクリメント演算子。
演算子 名称
v++ インクリメント ES1 let v=0; [v++, v] [0, 1]
++v インクリメント ES1 let v=0; [++v, v] [1, 1]
v-- デクリメント ES1 let v=1; [v--, v] [1, 0]
--v デクリメント ES1 let v=1; [--v, v] [0, 0]

次に、これらの演算子の使用例を見ていきます。

プリフィックス++とプリフィックス--は、オペランドを変更してから返します。

let foo = 3;
assert.equal(++foo, 4);
assert.equal(foo, 4);

let bar = 3;
assert.equal(--bar, 2);
assert.equal(bar, 2);

ポストフィックス++とポストフィックス--は、オペランドを返してから変更します。

let foo = 3;
assert.equal(foo++, 3);
assert.equal(foo, 4);

let bar = 3;
assert.equal(bar--, 3);
assert.equal(bar, 2);
16.3.3.1 オペランド: 変数だけではありません

これらの演算子をプロパティ値にも適用できます。

const obj = { a: 1 };
++obj.a;
assert.equal(obj.a, 2);

そして、配列要素にも。

const arr = [ 4 ];
arr[0]++;
assert.deepEqual(arr, [5]);

  演習: 数値演算子

exercises/numbers-math/is_odd_test.mjs

16.4 数値への変換

値を数値に変換する3つの方法があります。

推奨事項: 記述的なNumber()を使用してください。表 8 に、その動作の概要を示します。

表 8: 値の数値への変換。
x Number(x)
undefined NaN
null 0
boolean false 0, true 1
number x (変更なし)
bigint -1n -1, 1n 1 など
string '' 0
その他 先頭/末尾の空白を無視した解析された数値
symbol TypeErrorをスローします
object 構成可能 (例: .valueOf() を介して)

assert.equal(Number(123.45), 123.45);

assert.equal(Number(''), 0);
assert.equal(Number('\n 123.45 \t'), 123.45);
assert.equal(Number('xyz'), NaN);

assert.equal(Number(-123n), -123);

オブジェクトが数値に変換される方法は、構成可能です。たとえば、.valueOf() をオーバーライドすることで構成できます。

> Number({ valueOf() { return 123 } })
123

  演習: 数値への変換

exercises/numbers-math/parse_number_test.mjs

16.5 エラー値

エラーが発生したときに2つの数値が返されます。

16.5.1 エラー値: NaN

NaNは「数値ではない」の略です。皮肉なことに、JavaScriptはそれを数値と見なします。

> typeof NaN
'number'

いつNaNが返されますか?

数値を解析できない場合、NaNが返されます。

> Number('$$$')
NaN
> Number(undefined)
NaN

演算を実行できない場合、NaNが返されます。

> Math.log(-1)
NaN
> Math.sqrt(-1)
NaN

オペランドまたは引数がNaNの場合、NaNが返されます(エラーを伝播するため)。

> NaN - 3
NaN
> 7 ** NaN
NaN
16.5.1.1 NaNの確認

NaNは、それ自身と厳密に等しくない唯一のJavaScript値です。

const n = NaN;
assert.equal(n === n, false);

xNaNかどうかを確認するいくつかの方法があります。

const x = NaN;

assert.equal(Number.isNaN(x), true); // preferred
assert.equal(Object.is(x, NaN), true);
assert.equal(x !== x, true);

最後の行では、比較の特殊性を使用してNaNを検出しています。

16.5.1.2 配列内のNaNの検索

一部の配列メソッドはNaNを見つけることができません。

> [NaN].indexOf(NaN)
-1

他のものは見つけることができます。

> [NaN].includes(NaN)
true
> [NaN].findIndex(x => Number.isNaN(x))
0
> [NaN].find(x => Number.isNaN(x))
NaN

残念ながら、簡単な経験則はありません。各メソッドがNaNをどのように処理するかを確認する必要があります。

16.5.2 エラー値: Infinity

いつエラー値Infinityが返されますか?

数値が大きすぎる場合、Infinityが返されます。

> Math.pow(2, 1023)
8.98846567431158e+307
> Math.pow(2, 1024)
Infinity

ゼロ除算の場合、Infinityが返されます。

> 5 / 0
Infinity
> -5 / 0
-Infinity
16.5.2.1 デフォルト値としてのInfinity

Infinityは他のすべての数値(NaNを除く)よりも大きいため、優れたデフォルト値になります。

function findMinimum(numbers) {
  let min = Infinity;
  for (const n of numbers) {
    if (n < min) min = n;
  }
  return min;
}

assert.equal(findMinimum([5, -1, 2]), -1);
assert.equal(findMinimum([]), Infinity);
16.5.2.2 Infinityの確認

xInfinityかどうかを確認する2つの一般的な方法があります。

const x = Infinity;

assert.equal(x === Infinity, true);
assert.equal(Number.isFinite(x), false);

  演習: 数値の比較

exercises/numbers-math/find_max_test.mjs

16.6 数値の精度: 小数点以下の数値に注意

JavaScript の浮動小数点数は内部的に2進数(IEEE 754 標準準拠)で表現されます。そのため、10進数の小数(10進数)は常に正確に表現できるとは限りません。

> 0.1 + 0.2
0.30000000000000004
> 1.3 * 3
3.9000000000000004
> 1.4 * 100000000000000
139999999999999.98

したがって、JavaScript で算術演算を行う際には、丸め誤差を考慮する必要があります。

この現象の説明については、以下をお読みください。

  クイズ:基礎

クイズアプリ を参照してください。

16.7 (上級)

この章の残りのセクションはすべて上級者向けです。

16.8 背景:浮動小数点数の精度

JavaScript では、数値の計算が常に正しい結果を生成するとは限りません。例えば、

> 0.1 + 0.2
0.30000000000000004

その理由を理解するには、JavaScript が内部的に浮動小数点数をどのように表現しているかを調べることが必要です。そのためには、合計64ビットのストレージ(倍精度)を占める3つの整数が使用されます。

構成要素 サイズ 整数範囲
符号 1 ビット [0, 1]
仮数部 52 ビット [0, 252−1]
指数部 11 ビット [−1023, 1024]

これらの整数によって表される浮動小数点数は、次のように計算されます。

(–1)sign × 0b1.fraction × 2exponent

この表現では、2番目の構成要素(仮数部を含む)は常に先頭に1を持つため、ゼロをエンコードできません。したがって、ゼロは特別な指数-1023と仮数0でエンコードされます。

16.8.1 浮動小数点数の簡略化された表現

以降の議論を容易にするために、前の表現を簡略化します。

新しい表現は次のようになります。

mantissa × 10exponent

いくつかの浮動小数点数について、この表現を試してみましょう。

負の指数を持つ表現は、分母の正の指数を持つ分数として書くこともできます。

> 15 * (10 ** -1) === 15 / (10 ** 1)
true
> 25 * (10 ** -2) === 25 / (10 ** 2)
true

これらの分数により、エンコードできない数値が存在する理由が理解しやすくなります。

この小旅行を終えるために、基数2に戻ります。

これで、0.1 + 0.2 が正しい結果を生成しない理由がわかります。内部的には、どちらのオペランドも正確に表現できません。

10進数の小数で正確に計算するには、内部的に基数10に切り替える必要があります。多くのプログラミング言語では、基数2がデフォルトで、基数10はオプションです。例えば、JavaにはBigDecimalクラスがあり、Pythonにはdecimalモジュールがあります。JavaScriptにも同様の機能を追加する計画があります。ECMAScript 提案「Decimal」

16.9 JavaScript の整数

整数は、小数点以下の桁がない通常の(浮動小数点)数です。

> 1 === 1.0
true
> Number.isInteger(1.0)
true

このセクションでは、これらの擬似整数を取り扱うためのいくつかのツールを見ていきます。JavaScript は、真の整数であるbigintもサポートしています。

16.9.1 整数への変換

数値を整数に変換する推奨される方法は、Mathオブジェクトの丸めメソッドのいずれかを使用することです。

丸めに関する詳細については、§17.3「丸め」を参照してください。

16.9.2 JavaScript の整数の範囲

JavaScript の重要な整数の範囲を以下に示します。

16.9.3 安全な整数

これは、JavaScript で安全な整数の範囲です(53ビット + 符号)。

[–(253)+1, 253–1]

整数は、正確に1つの JavaScript 数で表現される場合、安全です。JavaScript の数は、分数に2の指数乗を掛けたものとしてエンコードされるため、より大きな整数も表現できますが、それらの間にはギャップがあります。

例(18014398509481984 は 254

> 18014398509481984
18014398509481984
> 18014398509481985
18014398509481984
> 18014398509481986
18014398509481984
> 18014398509481987
18014398509481988

Number の次のプロパティは、整数が安全かどうかを判断するのに役立ちます。

assert.equal(Number.MAX_SAFE_INTEGER, (2 ** 53) - 1);
assert.equal(Number.MIN_SAFE_INTEGER, -Number.MAX_SAFE_INTEGER);

assert.equal(Number.isSafeInteger(5), true);
assert.equal(Number.isSafeInteger('5'), false);
assert.equal(Number.isSafeInteger(5.1), false);
assert.equal(Number.isSafeInteger(Number.MAX_SAFE_INTEGER), true);
assert.equal(Number.isSafeInteger(Number.MAX_SAFE_INTEGER+1), false);

  演習:安全な整数の検出

exercises/numbers-math/is_safe_integer_test.mjs

16.9.3.1 安全な計算

安全でない整数を含む計算を見てみましょう。

次の結果は、両方のオペランドが安全であるにもかかわらず、不正確で安全ではありません。

> 9007199254740990 + 3
9007199254740992

次の結果は安全ですが、不正確です。最初のオペランドは安全ではなく、2番目のオペランドは安全です。

> 9007199254740995 - 10
9007199254740986

したがって、式a op bの結果は、次の場合にのみ正しいです。

isSafeInteger(a) && isSafeInteger(b) && isSafeInteger(a op b)

つまり、両方のオペランドと結果が安全である必要があります。

16.10 ビット演算子

16.10.1 内部的には、ビット演算子は32ビット整数で動作します。

内部的には、JavaScript のビット演算子は 32 ビット整数で動作します。それらは次の手順で結果を生成します。

16.10.1.1 オペランドと結果の型

各ビット演算子について、この本ではそのオペランドと結果の型について説明しています。各型は常に次の2つのいずれかです。

説明 サイズ 範囲
Int32 符号付き32ビット整数 32ビット(符号を含む) [−231, 231)
Uint32 符号なし32ビット整数 32ビット [0, 232)

前述の手順を考慮すると、ビット演算子は内部的に符号なし32ビット整数で動作する(手順「計算」)と仮定し、Int32とUint32は、JavaScript 数値が整数との間でどのように変換されるか(手順「入力」と「出力」)にのみ影響することをお勧めします。

16.10.1.2 JavaScript 数値を符号なし32ビット整数として表示する

ビット演算子を調査する際に、JavaScript 数値を2進表記の符号なし32ビット整数として表示することが時々役立ちます。それがb32()の役割です(その実装は後で示します)。

assert.equal(
  b32(-1),
  '11111111111111111111111111111111');
assert.equal(
  b32(1),
  '00000000000000000000000000000001');
assert.equal(
  b32(2 ** 31),
  '10000000000000000000000000000000');

16.10.2 ビット否定

表9:ビット否定演算子。
演算 名称 型シグネチャ
~num ビット否定、1の補数 Int32 Int32 ES1

ビット否定演算子(表9)は、オペランドの各2進数を反転します。

> b32(~0b100)
'11111111111111111111111111111011'

このいわゆる1の補数は、いくつかの算術演算に対して負の数に似ています。例えば、整数をその1の補数に加算すると、常に-1になります。

> 4 + ~4
-1
> -11 + ~-11
-1

16.10.3 2項ビット演算子

表10:2項ビット演算子。
演算 名称 型シグネチャ
num1 & num2 ビットごとの AND Int32 × Int32 Int32 ES1
num1 ¦ num2 ビットごとの OR Int32 × Int32 Int32 ES1
num1 ^ num2 ビットごとの XOR Int32 × Int32 Int32 ES1

2項ビット演算子(表10)は、オペランドのビットを組み合わせて結果を生成します。

> (0b1010 & 0b0011).toString(2).padStart(4, '0')
'0010'
> (0b1010 | 0b0011).toString(2).padStart(4, '0')
'1011'
> (0b1010 ^ 0b0011).toString(2).padStart(4, '0')
'1001'

16.10.4 ビットシフト演算子

表11:ビットシフト演算子。
演算 名称 型シグネチャ
num << count 左シフト Int32 × Uint32 Int32 ES1
num >> count 符号付き右シフト Int32 × Uint32 Int32 ES1
num >>> count 符号なし右シフト Uint32 × Uint32 Uint32 ES1

シフト演算子(表11)は、2進数を左または右に移動します。

> (0b10 << 1).toString(2)
'100'

>> は最上位ビットを保持し、>>> は保持しません。

> b32(0b10000000000000000000000000000010 >> 1)
'11000000000000000000000000000001'
> b32(0b10000000000000000000000000000010 >>> 1)
'01000000000000000000000000000001'

16.10.5 b32():符号なし32ビット整数を2進表記で表示する

これまで何度かb32()を使用しました。次のコードはその実装です。

/**
 * Return a string representing n as a 32-bit unsigned integer,
 * in binary notation.
 */
function b32(n) {
  // >>> ensures highest bit isn’t interpreted as a sign
  return (n >>> 0).toString(2).padStart(32, '0');
}
assert.equal(
  b32(6),
  '00000000000000000000000000000110');

n >>> 0 は、nを右に0ビットシフトすることを意味します。したがって、原則として>>>演算子は何も行いませんが、それでもnを符号なし32ビット整数に強制変換します。

> 12 >>> 0
12
> -12 >>> 0
4294967284
> (2**32 + 1) >>> 0
1

16.11 クイックリファレンス:数値

16.11.1 数値に関するグローバル関数

JavaScript には、数値に関する次の4つのグローバル関数があります。

ただし、落とし穴が少ないNumberの対応するメソッド(Number.isFinite()など)を使用する方が優れています。これらはES6で導入され、以下で説明します。

16.11.2 Numberの静的プロパティ

16.11.3 `Number`の静的メソッド

16.11.4 `Number.prototype`のメソッド

(`Number.prototype`は、数値のメソッドが格納されている場所です。)

16.11.5 出典

  クイズ:上級

クイズアプリ を参照してください。