web-dev-qa-db-ja.com

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

コンピュータは伝統的にゼロから始まる数値を集計します。たとえば、Cベースのプログラミング言語の配列は、インデックス0から始まります。

これにはどのような歴史的な理由があり、ゼロから数えることは1から数えることに対してどのような実際的な利点がありますか?

注:この質問は、単なる意見ではなく、十分に説明された技術的な回答を求め、プログラミングだけでなくコンピュータ全般を対象としています。この質問は、プログラマーの質問 "構造体/配列がゼロベースなのはなぜですか?" を拡張したものです。

54
bwDraco

配列を0から数えると、各要素のメモリアドレスの計算が簡単になります。

配列がメモリの特定の位置(アドレスと呼ばれます)に格納されている場合、各要素の位置は次のように計算できます。

element(n) = address + n * size_of_the_element

最初の要素を最初に考えると、計算は次のようになります。

element(n) = address + (n-1) * size_of_the_element

大きな違いはありませんが、アクセスごとに不要な減算が追加されます。

編集

  • オフセットとしての配列インデックスの使用は必須ではなく、単なる習慣です。最初の要素のオフセットはシステムによって非表示にされ、要素を割り当てて参照するときに考慮されます。

  • Dijkstra 「なぜ番号付けはゼロから始める必要があるのか​​」( pdf )の論文を発表しました。ここで、0から始める方が良い選択である理由を説明しています。ゼロから開始すると、範囲をより適切に表現できます。

88
Matteo

以下の原則は10進数と他のすべての基数に適用されますが、コンピューターで0から数えることは、コンピューターで使用される数値を表す固定桁の2進法から自然に容易に理解できます。 8ビットの場合、表現できる1と0の組み合わせは256通りあります。これらの8ビットを使用して1〜256の数値を表すことができますが、これは数学ではそれ自体として有用な0を除外するため、0〜255の数値を表すために使用されます。

これにより、0(バイナリ表現ではすべて0)から255(8ビット数ではすべて1)までの自然順序の前例がすでに設定されています。番号を表すシステムを考えると、0はシステムの「最初の」番号であり、1は「2番目の」番号であり、以降も同様であるため、0から開始することには意味があります。

コンピューターで0から始めるのが非常に便利であるもう1つの理由は、オフセットの概念によるものです。オフセットは、メモリまたはハードディスクまたはその他の「アドレス可能な」媒体内の場所からの距離を表す数値です。コンピュータでは、事実上すべてのデータが線形に格納されます。つまり、データには最初のバイト、2番目のバイトなどの順序があります。オフセットを介してデータの「領域」の場所を表すと便利です。データブロックの最初のバイトは何ですか?これはオフセット「0」にあります。これは、データブロックの最初のバイトの0バイト後にあることを意味します。 「1」で最初のバイトを指定することは可能ですが、これにより、いくつかの理由により、データの表現が複雑になります。

  • データのアドレス指定に0を使用しないことで、8ビットの数値でアドレス指定できるものの数を1つ減らします。
  • データアクセスのハードウェアレベルで必要なオフセットを計算するには、ある時点で、番号付けから1を減算する必要があり、これにより複雑さが生じます。
  • データのブロックへのポインターは常に最初のブロックを指すため、0から開始すると算術は簡単です(つまり、最初のデータクラスターの最初のブロックの最初のバイトは、0から開始すると0 + 0 + 0になります。 、1から開始すると1 + 1 + 1-1 -1になります。この例のようにネストされたデータ構造で1から開始すると、この計算は混乱する可能性があります。
38
Dougvj

私自身のような肘掛け椅子の哲学者のための機会がスーパーユーザーにやって来るとは決して思っていませんでした。ここでは根本的な誤解があります。なぜなら、非哲学者は細かい詳細をスキップする傾向があるからです。つまり、コンピュータはゼロからカウントされませんが、位置の名称はゼロから始まります。

コンピュータと人間の(任意の)カウント手法との間のこの認識された不一致について、混乱はありません。質問を分解してみましょう。

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

  • 彼らはゼロから数えません

コンピュータはゼロから始まる値を集計します。たとえば、Cの配列です。

  • index(位置の指標、タリー)はゼロから始まります。インデックス0に単一の要素がある配列内の要素のcountoneです

ゼロは、何かの隙間やスケールの中点を表すのに実用的です。ゼロの定義では不可能であるため、カウントには何も実用的ではありません。

スケールの中間点と同じ意味で、ゼロはコレクションのまさにエッジ(絶対的な始まり)を表すために使用できます。 「集計値」と「ゼロからのカウント」の間で一貫性がないため、質問は意味がありません。

つまり、コンピューターはゼロから数えますが、1から数えます。 2つの単語の意味は異なります。

tally[tal-ee]

名詞

  1. アカウントまたは計算;デビットおよびクレジットの記録、ゲームのスコアなど。
  2. スコアまたはアカウントが保持されているもの。
  3. 記録されたアイテムの数またはグループ。

カウント[kount]

動詞(オブジェクトで使用)

  1. (コレクションの個別のユニットまたはグループ)を1つずつチェックして、総数を決定します。合計;列挙:彼はチケットを数え、10枚持っていることがわかりました。
  2. 計算する;計算する;計算。
  3. 以下の数字をリストまたは名前を付けるには:目を閉じて、10を数えます。

(dictionary.com)


実用的な理由はDougvjによって適切に説明されています。そこに追加することは何もありません。もしも私たちが60年代のCS教授に歴史的経歴を与えることができれば...

これは以前、 " prof.dr。Edsger W. Dijkstra "- Burroughs 1982年8月11日付けの手紙のリサーチフェロー:c.f. EWD831

タイトル:番号付けがゼロから始まる必要がある理由「一方の規約をもう一方の規約よりも好む理由はありますか?はい、あります...」

Dijkstraが ALGOL 68 設計チームに1968年まで遅くまでいたことにも注意してください。ALGOL68は、0、1、またはプログラマーがアルゴリズムに適切であると見なす任意の数の配列を許可します。 c.f. ( "The Making of ALGOL 68" recounts '「三角形の配列を定義できますか?」誰か(Tony Hoare?)が割り込みました。「三角形だけでなく、楕円形でも」Aadが答え、その方法を示しました。 )

具体的には、ALGOL68では、配列(&行列)がスライスされると、インデックス@ 1が取得されるため、[1:...]配列に偏ります。しかし "1st"下限をから開始するように移動できます" 0番目"" @ 0 "を指定することによる位置。たとえば、ベクトルx [4:99 @ 2]、行列y [4:99 @ 1,4:99 @ 0]。同様に、デフォルト/バイアスfrom1 indo〜odループ(「from0 "は明示的に記述されています)、および整数の場合は1からcaseiin〜、〜、〜esacおよび$ c(〜、〜、〜) $choice句。

1968年3月のドラフトレポート( MR9 )に関するダイクストラのコメントと彼の主張は、おそらくプレユーズネットの炎戦を引き起こしたようです: "文法的ではないが愛らしい文章があり、非常に文法的であるが嫌な文章もあります。これは表面的な人には説明できないものです。"EWD23

ALGOL 68最終報告書(FR)は 1968年12月20日 に出され、ミュンヘン会議で憤慨し、ワーキンググループに採択されました。その後、ユネスコの総会で承認されたレポート [〜#〜] ifip [〜#〜] が発行されました。

12月23日頃(?)1968ダイクストラ、ダンカン、ガーウィック、 HoareRandell 、Seegmuller、Turski、Woodger、Garwickが AB31.1.1.1 "に署名マイノリティレポート」、7ページ (1970年発行)。

13
NevilleDNZ

他の誰かが持ち出した距離の類推は、非常に実用的な例に役立ちます。

「あなたの家は最寄りのガソリンスタンドからどれくらい離れていますか?」

「1マイル」

「あなたはガソリンスタンドに住んでいますか?」

「いいえ、ガソリンスタンドに住んでいたら、0マイルになるでしょう」

「なぜ1からではなく0から数えるのですか?」

もう1つの良い例は誕生日です。誰かが生まれた日が1歳であるとは言いませんが、1年後だと言います。

20、2001、2002、2003、2004は5年ですが、うるう年または米国大統領選挙は4年ごとと言います。 (ちなみに、ローマ人はこれをしばらく間違え、うるう年が近すぎました)

私の要点は、現実の世界では常にゼロから「カウント」することです。「[配列の開始]の後の位置の数は、必要な要素です」は、単にゼロからのカウントで答える質問です多くのコンピュータプログラムで。最初の要素が開始の1つ後の位置であるとは言わないでしょう開始ですか?それがisの始まりです。

10
Random832

他の人がすでに言ったようにコンピュータはゼロから数えません

一部の言語は0からインデックスを作成します。0からインデックスを作成すると、2つの主な利点があります。

  1. ポインタから最初の位置へのオフセットとして解釈できるため、自然な方法でアセンブリに変換されます。

  2. ネガが欲しいときに変になってしまうことはありません。 1BCと1ADの間には何年ありますか?なし。 BCは実質的に負の日付ですが、ゼロ年はありません。 0ADがあった場合、ここでは問題はありません。セット内の最初の要素を単純に+1と定義している科学のいたるところに同じ問題が見られます。

6
Jack Aidley

カウントは自然にゼロから始まります

かごの中のリンゴを数えるアルゴリズムは次のとおりです。

count := 0

for each Apple in basket
   count := count + 1

上記の実行後、countはリンゴの数を保持します。バスケットが空になる可能性があるため、ゼロになる場合があります。

1か月間クレジットカードを使用しなかった場合、1ドルの請求書を受け取りますか?または1セント?

車の走行距離計のトリップメーターをリセットすると、0001または0000になりますか?

配列は同じデータの複数のビューを提供できます

それぞれが16ビットワードdで構成される32ビット構造wの配列を考えます。各ワードは2つの8ビットバイトbで構成されています。インデックスがゼロの場合、オーバーレイは非常に便利に見えます。

d: |   0   |   1   |
w: | 0 | 1 | 2 | 3 |
b: |0|1|2|3|4|5|6|7|

32ビットオブジェクトd[1]は、ワードアドレスw[2]と同じです。これは、インデックスに2を掛けることで簡単に計算できます。これは、32ビットオブジェクトと16ビットオブジェクトのサイズの比率です。また、バイトアドレッシングではb[4]です。

これは、バイト、ワード、ダブルワードなどのすべての測定単位でゼロがゼロであるため機能します。

上の図を見てください。単位の変換が直感的な定規のように見えます。

1つのベースインデックスを使用すると、機能しなくなります。

d: |   1   |   2   |
w: | 1 | 2 | 3 | 4 |
b: |1|2|3|4|5|6|7|8|

ここで、dインデックスを取得するためにwインデックスを2で単純に乗算することはできません。また、bインデックスを取得するために4で乗算することはできません。単位間の変換は不器用になります。たとえば、d[2]からb[4]に移動するには、((2 - 1) * 4) + 1 = 5を計算する必要があります。

厄介な1バイアスをd単位で差し引いてから、自然なゼロベースの座標系でスケーリングを行い、厄介な1をb単位で加算し直す必要があります。 同じではないことに注意してください1!ワード幅を2倍にしますが、1バイト幅を追加します

データの異なるビュー間での変換は、摂氏と華氏の変換のようなものになります。

1ベースの配列は実装レベルで簡単に処理できると言う人は、1の単純な減算があるため、自分自身をだますことができます。これは、異なるデータタイプ間でスケーリング計算を行わない場合にのみ当てはまります。このような計算は、データに対して柔軟なビューを持つプログラム(たとえば、1次元としてもアクセスされる多次元配列)で発生します。 )またはストレージを操作します:たとえば、メモリアロケーター、ファイルシステム、またはビデオフレームバッファーライブラリ。

数字の最小化

どのような基数でも、最小の桁を使用して、その基数の累乗である値の範囲を実装する場合は、ゼロから開始する必要があります。たとえば、10を基数とする場合、0から999までの1000の異なる値を得るには3桁で十分です。1から開始すると、1つの値だけオーバーフローし、4桁が必要になります。

バイナリの桁数はハードウェアアドレスラインに変換されるため、これはコンピュータでは重要です。たとえば、a ROM 256ワードのチップは0から255までアドレス指定でき、8ビットが必要です:00000000から11111111。1から256までアドレス指定されている場合、9ビットが必要です。 。回路基板または集積回路に無駄なアドレストレースをもう1つ追加する必要があります。そのため、実際に発生する可能性があるのは、0がと呼ばれるそのチップにアクセスするためのソフトウェアAPIレベルで1。Word1の要求は実際には8ビットのアドレスバスに00000000を配置します。そうでない場合、1の要求は予想どおりアドレス00000001に変換されますが、256の要求は9ビットアドレス100000000ではなく、未使用の8ビットアドレス00000000にマップします。これらのバグを噛むクラッジは両方とも、問題を探すための実際のソリューションです。 、ハードウェア、ソフトウェア、およびすべてのユーザーインターフェイスとドキュメントで一貫して0〜255を使用することで完全に回避されます。

1ベースの変位は基本的に愚かです

たとえば、西洋音楽理論を考えてみましょう。 sevenの注記があるダイアトニックスケールがありますが、それらがカバーするスペースをoctave!間隔の反転はnineの規則に従います。たとえば、3番目の反転は6番目です(9から3を引く)。したがって、3つの異なる数字が非常にシンプルなものとして機能します。7(スケール内のノート)、8(オクターブ)、および9(から反転して反転)。

7つのノートがセプタムまたはヘプターブを作成し、間隔がゼロベースの場合、7から減算して反転します。 7に基づくすべて。

さらに、間隔を簡単に積み重ねることができます。現在のシステムでは、5度、4度、3分の1に跳躍した場合、これらを加算することはできません。結果の間隔は2つ少なくなります。これは12番目ではなく、実際には10番目です。各段階で、1を減算する必要があります。 5分の1、4分の1の上昇は9分ではなく、1オクターブだけです。

見事に設計された音楽システムでは、間隔を追加して、結果として生じる跳躍を決定することができます。同じノートで始まり、同じノートで終わる一連のノートは、回路の周りの電圧の法則に似た特性を持ちます。すべての間隔はゼロに追加されます。

音楽理論と作文はひどく時代遅れです。ろうそくの明かりで羽ペンを使って作曲された日々以来、そのほとんどは変わっていません。

1ベースのシステムは、0ベースのアレイを処理できない同じ人々を混乱させます

2000年が転じたとき、新しいミレニアムが始まっていない理由が多くの人々に混乱しました。 2001年までは始まらないと指摘した人々は、党の貧困者や党員とみなされた。結局、20歳になったとき、あなたは20代ですよね?あなたが21歳になったときではありません。2000年1月1日にミレニアムが始まったと思っている場合は、どのプログラミング言語でもゼロベースの配列について文句を言う権利はありません。彼らはあなたが好きなように正確に動作します。 (しかし、はい、1ベースのディスプレイスメントとアレイの支持者は、dweebsとparty-poopersです。世紀はXX00年に始まり、数千年はX000年に始まります。)

カレンダーはばかげていますが、少なくとも時刻はゼロベースです

時計の新しい分は:00秒から始まります。新しい時間はそれぞれ00:00分と秒から始まります。そして、少なくとも24時間制では、午前0時になると1日が動き、11:59:59が00:00:00に増加します。

したがって、午前0時から13:53:04のような時間を計算する場合は、13 * 3600 + 53 * 60 + 4を評価する必要があります。臆病な1の加算や減算はありません。

MIDIについての締めくくり

さて、ミュージシャン、それはおそらく技術的なものでさえ何ですか?

ミディ!メッセージの実際のワイヤー表現では、プログラムとチャネルにゼロベースの番号付けを使用しますが、ギアはそれを1ベースとして表示します!たとえば、プログラム0から127はほとんどのギアで1から128と呼ばれますが、0から127と呼ぶものや、ユーザーに選択肢を与えるものもあります。

プログラム71から80は、10の「バンク」と見なされます。たとえば、MIDIペダルなどです。フットスイッチには1から10までのラベルが付けられており、7番目のバンクにいる場合は、71から80までのプログラムを選択します。ただし、一部のデバイスまたは、コンピューターソフトウェアが1〜128のプログラム番号を0〜127として表示するか、ユーザーに選択肢を与えます!さらに悪いのは、1ベースのシステム、または1と0のベースを同時に使用して作成されたカオスですか。

MIDIチャネル番号は1〜16と呼ばれますが、0〜15のバイナリで表されます。 1ベースのプレゼンテーションに欠けているように、一部のギアはディスパッチスイッチを使用してチャネル番号を構成し、多くの場合、これらのスイッチはゼロベースのバイナリコードを使用します。したがって、チャネル3が必要な場合は、それを0010(バイナリ2)に切り替える必要があります。

3
Kaz

数値0は、数値、序数、メモリアドレスなど、さまざまな意味を表す場合があります。

「インデックスゼロ」は、プログラマがゼロから数えることを意味しません。これは、割り当てられたメモリブロックの最初の場所を示し、「0」はそのアドレスです。

Cでは、配列のループは次のように記述できます。

int arr[N];
for (i=0; arr[N]; ++i) {
...
}

同じ作業をC#で行うことができます。

Object[] arr;

for (Object o in arr) {
...
}

どちらの例にも数えられないと思います。

1
9dan

モジュロ

既存の良い答えがまだ言及していないことの1つ:ゼロベースのインデックス付けは、モジュロ演算と一緒にうまく機能するため、これらを組み合わせて循環リストを形成できます。たとえばのようなものについて考えてください

color = colors[i % colors.length]

すべての色が使用されるまで、各オブジェクト(iでインデックス付けされた)にリストcolorsとは異なる色が与えられ、その時点で最初から再び開始されます。同じことを1ベースのインデックス付けで表現するのはかなり不格好です。

color = colors[(i - 1) % colors.length + 1]

ラップアラウンドを使用した固定サイズの符号なし2項算術によって課される自動モジュロ演算は、これが理にかなっているもう1つの例です。

両方に対応

考慮すべきもう1つのことは、ゼロベースの配列の最初の要素をnotを使用するのは非常に簡単であることです。 (これはforeachスタイルの反復および配列全体を扱う類似の言語構成には当てはまりません。)多くのプログラマー(私自身も含む)は、無駄なスペースについて少し不便を感じるかもしれませんが、ほとんどの状況で非常に小さいので、これらの心配は根拠のないものです。一方、言語が1ベースのインデックスを使用している場合、多くのコードなしでインデックス0の要素をシミュレートする方法はありません。したがって、someの状況では、ゼロベースのインデックス付けは1ベースよりも優れており、ベースとしてゼロを選択しますeverywhereは、1ベースのあらゆる場所とは対照的に、より柔軟なアプローチです、また、構成可能な開始位置よりも一貫しています。

1
MvG

何かからの距離を表す場合は、ゼロから始めるのが実用的です。したがって、この配列では:

[4、9、25、49]

配列の開始から25までの距離は2です。そこに到達するには、2つのステップをスキップする必要があります。 4までの距離はゼロです。最初から移動する必要はありません。

距離(またはインデックス)を合計するときは、このように考えるのが現実的です。1つのステップ、次に0のステップ、2つのステップの順に進みます。私はインデックス1 + 0 + 2 = 3にいます。3つのステップをスキップすると、上の配列では49になります。

1
TV's Frank

コンピューターで数値がどのように表されるかを覚えておいてください。 byte変数を見てみましょう。 0は00000000として表されます1 バイナリで。 1は00000001です。2は00000010です。

byteに保存できる最小の数値は0であることに注意してください。配列のインデックスを1から始めた場合、256ではなく255の長さの配列があるため、システムは非効率になります。 Cプログラムは2進数(通常はints、配列インデックスでは_unsigned int_ s)にコンパイルされます。より効率的であるため、開始インデックスとして0を使用するのが自然なようです。

さらに、C++では、_a[p]_は*(a+p*n)に展開されます。ここで、nはデータ型のサイズです。言い換えると、_a[p]_は、「インデックス_a+n*p_にある要素を教えて」を意味します。 pが_1_で始まっている場合、インデックスaに空白/未使用部分があります。

1.もちろん、「なぜ」という明らかな疑問が生じます。 00000000を1に設定しないのはなぜですか?単純:00000000が0の場合、ハードウェアで2進加算(全加算器ユニットのカスケードによって実行)は簡単です。2進加算は、すべての算術演算の不可欠な部分です。 1を表す場合は、すべての数値から1を減算するようにコンパイラーに指示するか、最初に加数から1を減算して合計に戻すように加算回路を配線する必要があります。 (キャリービットが含まれている可能性があるため、後で1を減算することはできません)

1
Manishearth

私のプログラミング言語の概念クラスから正しく思い出せば...インデックスが0である言語とインデックスが1である言語は歴史的な理由と関係がありました。プログラミング言語の祖父であるALGOL-68は、実際には1インデックスであり、FortranやCOBOLなどの他のいくつかの「ビジネス」言語も同様でした。ただし、これらの言語の一部では、実際の開始インデックスを明示的に指定できます。これの興味深い表 here があります。

基本的に、「Ye Olde Days」に戻ると、数学者、科学者、その他の「学者」は通常0インデックス付き言語を使用していましたが、COBOLなどの言語のユーザーは、最初は役に立たないことに気付きました0から数えるので、これらの言語では1から始める方が理にかなっています(混乱が少ないように見えました)。

今あなたの質問がなぜcomputernot a language)がなぜ自然に始まるのかという理由について言及しているならゼロからカウントするには...まあそれは本当にバイナリに固有だと思います:ex:0000 =ゼロ0001 = 1 ...などなど...

1
unknownprotocol

コンピュータシステムでは、自然数(0から数える)と整数(1から数える)の両方を使用します。人々は整数で物事を数えるので、番号付けリストを直感的に理解でき、多くのプログラミング言語はそれを利用します:BASIC、COBOL、Fortran、Lua、Pascalはすべて1から数えます。教育は、シンプルで直感的なリストが有利です。

すべてを順番に処理するのではなく、データの構造の分析と操作を開始すると、整数は扱いにくくなります。数式やアルゴリズムでシーケンスを参照する必要がある場合、数学者が行うように、シーケンスに0から番号を付ける方が簡単でエラーが発生しにくくなります。、a1、aなど。そうでない場合は、正しいデータを取得するために+1と-1で調整する必要があることが多く、間違えやすく、バグが発生します。したがって、コンピュータサイエンティスト向けに設計された言語は、通常、自然数を使用します。C、Java、およびLISPはすべて0から数えます。

プログラミング言語を超えて、多くのコンピューターシステムは0から番号を付けます。なぜなら、それがコンピューター科学者が慣れていることだからです。また、1からの番号付けは非常に多くの潜行性のあるバグにつながるため、技術者以外のユーザー向けに厳密に設計されたインターフェース要素以外では、多くの人がバグを避けています。

0
Bradd Szonye

簡単な答えは、最初の数字が1ではなく、0であることです。

説明:任意のベースで複数桁の数値を計算する式は次のとおりです。

n = sum(i=0 to n, Di^i)

WHERE 
n = numeric result
i = index (starting with 0)
Di = is the digit at index i

10進法を考えてみましょう。これは、私たちが最も慣れているものです。

番号1234を見ると、次のように書くことができます。

4 x 10^0 = 4
3 x 10^1 = 30
2 x 10^2 = 200
1 x 10^3 = 1000

in other words, sum of digits raised to the power if their index.

つまり、コンピュータだけではなく、私たち、人々も0から数えます。

0
Metaphor

配列インデックスは、ベースメモリの場所から要素のメモリの場所までのオフセットです。要素iはBase + iです。最初の要素はベースの場所にあるため、場所0(ベース+ 0)にあります。

0
Metaphor

計算効率とは別に、カウントには別の側面もあります。シーケンスの各要素に連番を付ける方法は2つあります。

  1. 先行する(全体の)要素の数(基数)
  2. 要素の位置(序数)

人々の年齢は基本的な数字です:赤ちゃんが生まれてから1年目は0歳です。

日付の年は序数です。最初の年のAnno Domini(AD)では、年は1 ADです。 zeroth何もないように、0年はありません。

要素のインデックスが配列内のその位置を表すプログラミング言語(MatlabやMathematicaなど)は、first要素から数え始めます。他の言語(すべてのCベースの言語など)では、要素のインデックスは先行する要素の数であるため、first要素は0です。


もちろん、ゼロベースのインデックス付けがより効率的であると述べるとき、 Matteoは部分的にのみ正しい です。

element(n) = address + n * element_size

すべての配列アドレスにすでに1つのelement_sizeが差し引かれていれば、1ベースのインデックス付けも同じくらい効率的です。これは、配列が割り当てられたときに実行できます。その場合、これは同じくらい高速です。

array_address = address - element_size
element(n) = array_address + n * element_size