コンテンツにスキップ

JavaScript/型付き配列

出典: フリー教科書『ウィキブックス(Wikibooks)』

型付き配列(かたつきはいれつ、Typed array)は特定のデータ型を持つ要素しか扱えないという制限があります。 つまり、整数型のみ、あるいは浮動小数点数型のみを扱うことができます。 型付き配列を使うことで、メモリの効率的な利用や、高速なデータ処理が可能になるため、JavaScriptのプログラマにとっては非常に重要な知識と言えます。

概要

[編集]

型付き配列は、C言語の配列のように要素数を予め決め全ての要素の型が同じオブジェクトなインデックス可能なコレクションです。

型付き配列を使って浮動小数点数のビットパターンを表示する
const f64 = new Float64Array([0, 1, NaN, Infinity, -Infinity, Math.PI])
const b64 = new BigUint64Array(f64.buffer)
f64.forEach((x, i) =>
  console.log(`${(""+x).padStart(17," ")}: ${b64[i].toString(2).padStart(64, "0")}`) )
実行結果
                0: 0000000000000000000000000000000000000000000000000000000000000000
                1: 0011111111110000000000000000000000000000000000000000000000000000
              NaN: 0111111111111000000000000000000000000000000000000000000000000000
         Infinity: 0111111111110000000000000000000000000000000000000000000000000000
        -Infinity: 1111111111110000000000000000000000000000000000000000000000000000
3.141592653589793: 0100000000001001001000011111101101010100010001000010110100011000
行ごとに説明しましょう。
  1. Float64Arrayオブジェクトf64を [0, 1, NaN, Infinity, -Infinity, Math.PI] をパラメータとして生成
  2. BigUint64Arrayオブジェクトb64をFloat64Arrayオブジェクトのバッファを引数に生成
  3. Float64Arrayの要素を順に
  4.  BigUint64Arrayを使って二進文字列化

6種類のIEEE 75464ビット倍精度浮動小数点数のバイナリ表現を表示したもので、TypedArray を使うと内部表現へのアクセスが buffer プロパティを使って可能になる事を示す例となっています。

この様な ArrayBuffer を扱うためには、DataViewオブジェクトが用意されていますが今回は同じ64ビット同士にすることでエンディアンの問題を回避しました。

TypedArray オブジェクト

[編集]

TypedArray はコンストラクターとしては呼び出さず、Float64ArrayオブジェクトやBigUint64Arrayオブジェクトのような TypedArray オブジェクトのインスタンスを生成します。

// TypedArray オブジェクトの生成例

// Int8Array: 8ビット符号付き整数のTypedArrayオブジェクト
const int8Array = new Int8Array(4);
console.log(int8Array); // Int8Array [ 0, 0, 0, 0 ]

// Uint8Array: 8ビット符号なし整数のTypedArrayオブジェクト
const uint8Array = new Uint8Array([1, 2, 3, 4]);
console.log(uint8Array); // Uint8Array [ 1, 2, 3, 4 ]

// Uint8ClampedArray: オーバーフロー対策された8ビット符号なし整数のTypedArrayオブジェクト
const uint8ClampedArray = new Uint8ClampedArray([300, -50, 1000, 0]);
console.log(uint8ClampedArray); // Uint8ClampedArray [ 255, 0, 255, 0 ]

// Int16Array: 16ビット符号付き整数のTypedArrayオブジェクト
const int16Array = new Int16Array([32767, -32768, 0, 12345]);
console.log(int16Array); // Int16Array [ 32767, -32768, 0, 12345 ]

// Uint16Array: 16ビット符号なし整数のTypedArrayオブジェクト
const uint16Array = new Uint16Array([65535, 0, 1234, 9876]);
console.log(uint16Array); // Uint16Array [ 65535, 0, 1234, 9876 ]

// Int32Array: 32ビット符号付き整数のTypedArrayオブジェクト
const int32Array = new Int32Array([-2147483648, 0, 2147483647, 123456]);
console.log(int32Array); // Int32Array [ -2147483648, 0, 2147483647, 123456 ]

// Uint32Array: 32ビット符号なし整数のTypedArrayオブジェクト
const uint32Array = new Uint32Array([4294967295, 0, 987654321, 555555]);
console.log(uint32Array); // Uint32Array [ 4294967295, 0, 987654321, 555555 ]

// Float32Array: 32ビット単精度浮動小数点数のTypedArrayオブジェクト
const float32Array = new Float32Array([3.14, -0.5, 1.234, 7.89]);
console.log(float32Array); // Float32Array [ 3.140000104904175, -0.5, 1.2340000867843628, 7.889999866485596 ]

// Float64Array: 64ビット倍精度浮動小数点数のTypedArrayオブジェクト
const float64Array = new Float64Array([1.2345678901234567, -9876543.210, 0, 12345.6789]);
console.log(float64Array); // Float64Array [ 1.2345678901234567, -9876543.21, 0, 12345.6789 ]

// BigInt64Array: 64ビット符号付き整数のTypedArrayオブジェクト
const bigInt64Array = new BigInt64Array([BigInt('-9223372036854775808'), BigInt('0'), BigInt('9223372036854775807')]);
console.log(bigInt64Array); // BigInt64Array [ -9223372036854775808n, 0n, 9223372036854775807n ]

// BigUint64Array: 64ビット符号なし整数のTypedArrayオブジェクト
const bigUint64Array = new BigUint64Array([BigInt('18446744073709551615'), BigInt('0'), BigInt('1234567890123456789')]);
console.log(bigUint64Array); // BigUint64Array [ 18446744073709551615n, 0n, 1234567890123456789n ]
TypedArray
オブジェクト 要素型
Int8Array 8ビット符号付き整数
Uint8Array 8ビット符号なし整数
Uint8ClampedArray オーバーフロー対策された8ビット符号なし整数
Int16Array 16ビット符号付き整数
Uint16Array 16ビット符号なし整数
Int32Array 32ビット符号付き整数
Uint32Array 32ビット符号なし整数
Float32Array 32ビット単精度浮動小数点数
Float64Array 64ビット倍精度浮動小数点数組み込み数値プリミティブと同じ精度。
BigInt64Array 64ビット符号付き整数
BigUint64Array 64ビット符号なし整数

Uint8ClampedArray

[編集]

オーバーフロー対策と丸め処理を施された8ビット符号なし整数のTypedArray オブジェクト。

以下の変換規則に従います[1]

  • 数値に変換する
  • NaN, +0, -0, Infinity 及び -Infinity は+0とする
  • 0未満は0に丸める
  • 255を超えると255に丸める
  • 最近接偶数丸めを行う
最近接偶数丸め
最近接偶数丸め(round to even)は、丸めるべき値が整数部と小数部の境界にある場合、最も近い偶数に丸めるルールです。このルールは、統計的な観点から生まれており、偶数と奇数が均等に分布しているため、統計的な誤差を最小限に抑える効果があります。

最近接偶数丸めは、以下のような特徴があります

  1. 丸めるべき値が整数の場合は、そのままの整数に丸められます。
  2. 丸めるべき値が小数で、小数部が0.5の場合は、最も近い偶数に丸められます。例えば、0.5は0に、1.5は2に丸められます。
  3. 小数部が0.5である場合、整数部が偶数の場合は最も近い偶数に、整数部が奇数の場合は最も近い奇数に丸められます。
この丸め方は、統計的に誤差を最小化する効果があるため、科学計算や金融などの精度が求められる分野でよく使用されます。JavaScriptのUint8ClampedArrayの仕様においても、最近接偶数丸めが適用されています。

Canvas#imageData へのアクセスを使った高速化の節に、CanvasのimageDate.data(Uint8ClampedArrayオブジェクト)を直接操作して高速化をはかる例がある。

const ui8c = new Uint8ClampedArray(1);
[ NaN, +0, -0, -1, 256, 0.5, 0.5000000000001, 1.5, 1.5000000000001, ].forEach(x => console.log(ui8c[0] = x, ui8c[0]));
/*
 NaN 0
 0 0
 -0 0
 -1 0
 256 255
 0.5 0
 0.5000000000001 1
 */

メソッド

[編集]

TypedArray オブジェクトは、通常の JavaScript 配列と同様に、さまざまなメソッドを提供しています。以下は、主な TypedArray オブジェクトのメソッドのいくつかです。

共通のメソッド
  1. length
    配列の要素数を返します。
    const int32Array = new Int32Array([1, 2, 3, 4]);
    console.log(int32Array.length); // 4
    
  2. subarray(begin[, end])
    部分的な TypedArray を新しい TypedArray として返します。
    const int32Array = new Int32Array([1, 2, 3, 4]);
    const subArray = int32Array.subarray(1, 3);
    console.log(subArray); // Int32Array [ 2, 3 ]
    
  3. slice(begin[, end])
    部分的な TypedArray のコピーを新しい TypedArray として返します。
    const int32Array = new Int32Array([1, 2, 3, 4]);
    const slicedArray = int32Array.slice(1, 3);
    console.log(slicedArray); // Int32Array [ 2, 3 ]
    
数値演算関連のメソッド
  1. reduce(callback[, initialValue])
    配列の各要素に対してコールバック関数を適用し、単一の累積値を返します。
    const float64Array = new Float64Array([1.5, 2.5, 3.5]);
    const sum = float64Array.reduce((acc, val) => acc + val, 0);
    console.log(sum); // 7.5
    
  2. map(callback)

配列の各要素に対してコールバック関数を適用し、新しい TypedArray を返します。

  1. const int8Array = new Int8Array([1, 2, 3]);
    const squaredArray = int8Array.map(val => val * val);
    console.log(squaredArray); // Int8Array [ 1, 4, 9 ]
    
  2. filter(callback)
    配列の各要素に対してフィルタリングを行い、新しい TypedArray を返します。
    const int16Array = new Int16Array([10, -5, 7, -3]);
    const positiveNumbers = int16Array.filter(val => val > 0);
    console.log(positiveNumbers); // Int16Array [ 10, 7 ]
    

ArrayとTypedArrayのメソッドの差異

[編集]

ArrayとTypedArrayにはいくつかのメソッドが共通して存在しますが、いくつかの違いもあります。以下に、ArrayにあってTypedArrayにないメソッド、あるいはその逆をいくつか挙げます。

ArrayにあってTypedArrayにないメソッド

[編集]
  1. concat()
    TypedArrayには concat() メソッドが存在しません。代わりに TypedArray では、スプレッド構文を使用して結合します。
    // Arrayの例
    const array1 = [1, 2, 3];
    const array2 = [4, 5, 6];
    const newArray = array1.concat(array2);
    console.log(newArray); // [1, 2, 3, 4, 5, 6]
    
    // TypedArrayの例
    const typedArray1 = new Int32Array([1, 2, 3]);
    const typedArray2 = new Int32Array([4, 5, 6]);
    const newTypedArray = new Int32Array([...typedArray1, ...typedArray2]);
    console.log(newTypedArray); // Int32Array [1, 2, 3, 4, 5, 6]
    
  2. reverse()
    TypedArrayには reverse() メソッドが存在しません。代わりに、スプレッド構文や独自の方法を使用して逆順にします。
    // Arrayの例
    const array = [1, 2, 3];
    const reversedArray = array.reverse();
    console.log(reversedArray); // [3, 2, 1]
    
    // TypedArrayの例
    const typedArray = new Int32Array([1, 2, 3]);
    const reversedTypedArray = new Int32Array([...typedArray].reverse());
    console.log(reversedTypedArray); // Int32Array [3, 2, 1]
    

TypedArrayにあってArrayにないメソッド

[編集]
  1. set()
    TypedArrayは set() メソッドを提供しており、一方のTypedArrayから別のTypedArrayにデータをコピーできます。
    const sourceTypedArray = new Int32Array([1, 2, 3]);
    const targetTypedArray = new Int32Array(3);
    
    // TypedArrayからTypedArrayにデータをコピー
    targetTypedArray.set(sourceTypedArray);
    console.log(targetTypedArray); // Int32Array [1, 2, 3]
    
  2. subarray(begin[, end])
    TypedArrayは subarray() メソッドを提供しており、一部のTypedArrayを新しいTypedArrayとして取り出すことができます。
    const typedArray = new Int32Array([1, 2, 3, 4, 5]);
    
    // TypedArrayから一部を取り出す
    const subArray = typedArray.subarray(1, 4);
    console.log(subArray); // Int32Array [2, 3, 4]
    

このように、ArrayとTypedArrayは基本的な操作においては共通のメソッドを持っていますが、細かな違いがあります。注意して使用することが重要です。

スプレッド構文

[編集]

TypedArray オブジェクトでもスプレッド構文を使用することができます。スプレッド構文は、配列や TypedArray オブジェクトから要素を取り出して新しい配列や TypedArray を作成するための便利な構文です。

以下は、スプレッド構文を使用した TypedArray の例です:

// TypedArray オブジェクトの作成
const sourceArray = new Int32Array([1, 2, 3, 4]);

// スプレッド構文を使用して新しい TypedArray を作成
const newArray = new Int32Array([...sourceArray, 5, 6]);

console.log(newArray); // Int32Array [ 1, 2, 3, 4, 5, 6 ]

この例では、既存の sourceArray から要素を取り出し、新しい Int32Array オブジェクト newArray を作成しています。このようにして、スプレッド構文を用いて TypedArray の要素を効果的に結合できます。ただし、スプレッド構文を使用して TypedArray のコピーを作成する場合、パフォーマンスに注意する必要があります。

TypedArrayのユースケース

[編集]

TypedArrayはバイナリデータを効率的に扱うために設計されたJavaScriptの機能であり、主に以下のような場面で活用されます。

  1. 画像処理とキャンバス操作: TypedArrayはピクセルデータを効率的に操作するために使用されます。Uint8ClampedArrayは特に画像処理でよく使用され、Canvas APIの getImageData などで取得されたピクセルデータがこの型のTypedArrayとして提供されます。
    const canvas = document.getElementById('myCanvas');
    const context = canvas.getContext('2d');
    const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
    const pixelData = new Uint8ClampedArray(imageData.data.buffer);
    // pixelDataを操作して画像処理を行う
    
  2. 音声処理: 音声データもバイナリデータで表現されます。TypedArrayは音声信号の処理に使用され、Float32ArrayInt16Arrayがよく利用されます。
    const audioContext = new (window.AudioContext || window.webkitAudioContext)();
    const buffer = audioContext.createBuffer(1, 44100, 44100); // 1チャンネル, サンプリングレート44100
    const audioData = new Float32Array(buffer.getChannelData(0));
    // audioDataを操作して音声処理を行う
    
  3. ネットワーク通信: ネットワーク通信時には、バイナリデータを受け取り、TypedArrayを使用してデータの解析や処理を行います。WebSocketやFetch APIで受信したデータをTypedArrayに変換して扱うことがあります。
    fetch('https://example.com/data.bin')
      .then(response => response.arrayBuffer())
      .then(data => {
        const dataArray = new Uint8Array(data);
        // dataArrayを解析してデータ処理を行う
      });
    
  4. 3Dグラフィックス: WebGLやWebGPUなどの3DグラフィックスAPIでは、頂点データやテクスチャデータをTypedArrayとして扱います。これにより、高効率にバイナリデータを操作できます。
    const vertexData = new Float32Array([/* ... */]);
    const indexData = new Uint16Array([/* ... */]);
    // WebGLでvertexDataやindexDataを扱う
    
  5. 大規模なデータ処理: 大規模なデータセットやバイナリデータの処理において、TypedArrayはメモリ効率や演算速度の向上を提供します。これにより、数値計算が必要なアプリケーションやライブラリでパフォーマンスが向上します。

これらはTypedArrayが有用である一部のシーンであり、バイナリデータを効率的に取り扱うために欠かせない機能です。

TypedArrayのベストプラクティス

[編集]

TypedArrayを使用する際には、いくつかのベストプラクティスに従うことが重要です。以下は、TypedArrayの効果的な使用に関する一般的なベストプラクティスです。

  1. 型付き配列の正確なサイズを把握する: TypedArrayは固定サイズのバッファを持ちます。事前にデータサイズを正確に知っておくことが重要です。サイズを過小評価するとデータが失われる可能性があり、過大評価すると余分なメモリを使用することになります。
  2. バッファの再利用: TypedArrayのバッファを再利用することで、メモリの効率が向上します。余分なバッファの割り当てを避け、不要なガベージコレクションを回避できます。
    // バッファを再利用する例
    const buffer = new ArrayBuffer(8);
    const int32Array = new Int32Array(buffer);
    // 以後、同じバッファを再利用して異なるTypedArrayを作成
    const float32Array = new Float32Array(buffer);
    
  3. ビューの使用: DataViewを使用してTypedArrayのバッファを異なるビューで表示することができます。これは特にバイトオーダーや異なる型のデータにアクセスする際に便利です。
    const int32Array = new Int32Array([1, 2, 3, 4]);
    const dataView = new DataView(int32Array.buffer);
    const firstValue = dataView.getInt32(0, true); // リトルエンディアン
    
  4. バイナリデータの処理: TypedArrayは主にバイナリデータを扱うためにデザインされています。バイナリデータの読み書き、変換、処理にはTypedArrayのメソッドやビューを活用しましょう。
    const byteArray = new Uint8Array([1, 2, 3, 4, 5]);
    const uint32Array = new Uint32Array(byteArray.buffer);
    console.log(uint32Array); // Uint32Array [ 67305985 ]
    
  5. パフォーマンスの最適化: TypedArrayは高速な数値演算を提供しますが、無駄なメモリのコピーを避けるために注意が必要です。スプレッド構文やArray.from()を使うと、性能が低下する可能性があります。
    // パフォーマンスの観点からは避けるべき例
    const typedArray = new Float32Array([1, 2, 3]);
    const newArray = new Float32Array([...typedArray]); // 非効率的なコピー
    
    上記の代わりに、subarray()や直接TypedArrayのコンストラクタを使用すると効率的です。
    // 直接TypedArrayのコンストラクタを使用し効率に配慮した例。
    const typedArray = new Float32Array([1, 2, 3]);
    const newArray = new Float32Array(typedArray.buffer, typedArray.byteOffset, typedArray.length);
    

これらのベストプラクティスを踏まえてTypedArrayを使うことで、メモリの効率を向上させ、高速なバイナリデータ処理を実現できます。

脚註

[編集]
  1. ^ ECMA-262::7.1.12 ToUint8Clamp ( argument )

外部リンク

[編集]
このページ「JavaScript/型付き配列」は、まだ書きかけです。加筆・訂正など、協力いただける皆様の編集を心からお待ちしております。また、ご意見などがありましたら、お気軽にトークページへどうぞ。