와탭랩스 블로그 오픈 이벤트 😃
자세히 보기
Tech
2023-10-10
なぜコンピュータは0から数えるのですか?
Untitled.png

なぜコンピュータは0から数えるのですか?


プログラミングをすると、ほとんどの言語の配列などのインデックスを0から始まります。人間の立場では、数字を1からではなく0から数えるのが不自然だと思われます。

 

今回のポストでは、コンピュータが0から数字を数えるときに生じる利点についてご説明します。

 

1次元配列を2次元配列に変更する


インデックスを 0 から始める場合と 1 から始まる場合をそれぞれ C#とPascal で実現して違いを見てみましょう。ほとんどの言語が0からインデックスを開始するのとは異なり、Pascalは1から始める言語です。

 

0-based 索引付け


0から始めるインデックスを使用する場合、1次元配列のインデックスを2次元配列のインデックスに変換することは、次のように行われます。

 

int[] oneDimensional = new int[100];int[,] twoDimensional = new int[10, 10];for (int i = 0; i < 100; i++){twoDimensional[i / 10, i % 10] = oneDimensional[i];}

 

2次元配列のインデックスを[x, y]としたとき、yは 1次元配列のインデックスが 10が増加するたびに1が増加されます。xの場合は0から9まで繰り返すことになります。

 

1次元-i: [ 0] [ 1] [ 2] [ 3] [ 4] [ 5] [ 6] [ 7] [ 8] [ 9] [10]2次元-x: [ 0] [ 1] [ 2] [ 3] [ 4] [ 5] [ 6] [ 7] [ 8] [ 9] [ 0]==> i % 102次元-y: [ 0] [ 0] [ 0] [ 0] [ 0] [ 0] [ 0] [ 0] [ 0] [ 0] [ 1]==> 1 / 10

 

1-based 索引付け

1から始まるインデックスを使用する場合、1次元配列のインデックスを2次元配列のインデックスに変換するのは少し複雑です。追加の演算が必要になります。以下のPascalの例を見てみましょう。

 

vari: Integer;oneDimensional: array[1..100] of Integer;twoDimensional: array[1..10, 1..10] of Integer;beginfor i := 1 to 100 dotwoDimensional[(i - 1) div 10 + 1, (i - 1) mod 10 + 1] := oneDimensional[i];end.

 

Pascalの整数除算はdiv演算子を使用し、モジュロはmod演算子を使用します。下の図では都合上C#と同様に表現しています。

 

1次元-i: [ 1] [ 2] [ 3] [ 4] [ 5] [ 6] [ 7] [ 8] [ 9] [10] [11]2次元-x: [ 1] [ 2] [ 3] [ 4] [ 5] [ 6] [ 7] [ 8] [ 9] [10] [ 1] ==> (i-1)%10 + 12次元-y: [ 1] [ 1] [ 1] [ 1] [ 1] [ 1] [ 1] [ 1] [ 1] [ 1] [ 2] ==> (i-1)/10 + 1

 

2次元配列を1次元配列に変更する


0-based 索引付け


0から始まるインデックスを使用する場合、2次元配列のインデックスを1次元配列のインデックスに変換することは、次のように行われます。

 

int[,] twoDimensional = new int[10, 10];int[] oneDimensional = new int[100];for (int i = 0; i < 10; i++) {for (int j = 0; j < 10; j++) {oneDimensional[i * 10 + j] = twoDimensional[i, j];}}

 

1-based 索引付け


1から始まるインデックスを使用する場合、2次元配列のインデックスを1次元配列のインデックスに変換することは、次のように行われます。

 

vari, j: Integer;twoDimensional: array[1..10, 1..10] of Integer;oneDimensional: array[1..100] of Integer;beginfor i := 1 to 10 dofor j := 1 to 10 dooneDimensional[(i-1) * 10 + j] := twoDimensional[i, j];end.

 

この例からわかるように、1次元配列と2次元配列の切り替えは、0-based 索引付けがより簡単で直感的です。

 

一般的な状況では、プログラマは配列の次元遷移を気にする必要があることはあまりありません。しかし、プログラムが運営される内部状況では、これらの演算が頻繁に起こっています。したがって、0からインデックス開始するのがコンピュータの立場ではより自然的なことになります。

 

メモリアドレス演算とOffset

配列やポインタの基準点から各要素がどれだけ離れているかを把握することは、頻繁によく発生する演算です。特にポインタでは、特定の位置のデータに直接アクセスしたい場合が多いです。このような場合はoffsetを使用します。offsetは、ベースアドレスからどれだけ離れているかを示す値です。

 

ここで、offsetが0または1で始まる場合を比較して説明します。

 

offsetが1の場合

オフセットが1で始まる場合は、最初のバイト(または要素)にアクセスするために-1を指定する必要があります。これは、offset 値が実際に最初のバイトより 1 つ上にあるためです。

 

char *data = (char *)malloc(1024);// 最初のデータという意味で1を使用int offset = 1;// 最初のバイトにアクセスするためにoffsetから1を引いてください。char *theFirstByte = data + offset - 1;

 

offsetが0の場合


オフセットが0で始まる場合は、最初のバイトに直接アクセスできます。これは、追加の操作なしでポインタがすでにデータの始点を指しているためです。

 

char *data = (char *)malloc(1024);// 指す位置をすぐに参照するという意味int offset = 0;// 最初のバイトに直接アクセスします。char *theFirstByte = data + offset;

 

上記の2つの例を比較すると、offsetが0の場合がはるかに直感的で簡単であることがわかります。 実際に多くのプログラミング言語とシステムは0ベースのインデックスを使用します。これは、メモリアドレスの操作を簡素化し、コードの読みやすさを向上させるためです。

 

直感的な範囲の計算

今回は、範囲を計算する場合の違いを以下のように2つのケースをそれぞれ見ながら、0から始めたときと1から始めたときの違いを説明します。

 

(a) 0 ≤ i < 10(b) 1 ≤ i ≤ 10

 

メモリアドレスの演算を簡素化


配列の最初の要素のメモリアドレスをベースアドレスと考えると、0 ≤ i < 10の場合、追加の操作なしで直接その要素にアクセスできます。1 ≤ i ≤ 10では、メモリアドレスを計算するときに1を引く必要がある追加の操作が必要です。

 

範囲計算の直感性

0 ≤ i < 10 の場合、配列または文字列の先頭から特定の位置までスライスするときに、開始インデックスを指定する必要はありません。1≤i≤10では、開始位置の1を常に指定する必要があります。

 

連続範囲の統合

0 ≤ i < 10と10 ≤ i < 20のように、連続した範囲は前の範囲の最後の値からすぐに始まります。一方、1 ≤ i ≤ 10 の後の連続範囲は11 ≤ i ≤ 20となり、開始値と終了値の両方を調整する必要があります。

 

要約すると、0≦i<10のような0から始まる索引付けは、プログラミングにおける演算の簡潔さ、直感性、及びメモリアクセスの効率を提供します。一方、1 ≤ i ≤ 10などの1から始まる索引付けは、追加の操作または明示的な初期化を必要とし、連続した範囲表現が複雑になる可能性があります。

와탭 모니터링을 무료로 체험해보세요!