Retranslate the complete Japanese online edition from Chinese (#2008)

* Retranslate the complete Japanese online edition from Chinese

* Fit Japanese homepage labels and localize heading permalinks

* Point Japanese contribution instructions to the Japanese preview

* Replace locale conditionals with multilingual UI tables
This commit is contained in:
Yudong Jin
2026-10-30 02:12:26 +08:00
committed by GitHub
parent 455e8c1281
commit 7257641e0e
1987 changed files with 23531 additions and 23261 deletions

View File

@@ -1,66 +1,66 @@
# 基本データ型
コンピュータ内のデータについて考えるとき、テキスト、画像、動画、音声、3D モデルなど、さまざまな形態を思い浮かべます。これらのデータの構成形式はそれぞれ異なりますが、いずれも各種の基本データ型によって成り立っています。
コンピュータのデータというと、文章、画像、動画、音声、3D モデルなどが思い浮かびます。形はさまざまですが、いずれも基本データ型を組み合わせて作られています。
**基本データ型は CPU が直接演算できる型**であり、アルゴリズムの中で直接使われます。主なものは次のとおりです。
**基本データ型は、CPU が直接演算できる型**で、アルゴリズムでもそのまま使います。主に次のものがあります。
- 整数型 `byte`、`short`、`int`、`long` 。
- 浮動小数点数型 `float`、`double` ,小数を表すために使います。
- 文字型 `char` ,各言語の文字、句読点、さらには絵文字などを表すために使います。
- 真偽値型 `bool` ,真か偽かの判定を表すために使います。
- 整数型:`byte`、`short`、`int`、`long`。
- 浮動小数点型:`float`、`double`。小数を表します。
- 文字型:`char`。各言語の文字、句読点、さらには絵文字などを表します。
- ブール型:`bool`。「はい」「いいえ」の判断を表します。
**基本データ型はコンピュータ内で 2 進数の形で格納されます**。1 つの二進桁は $1$ ビットです。現代のほとんどのオペレーティングシステムでは、$1$ バイト(byte)は $8$ ビット(bit)で構成されます。
**基本データ型は、コンピュータ内では二進数として保存されます**。二進数の 1 桁が $1$ ビットです。現代のほとんどの OS では、$1$ バイト(byte)は $8$ ビット(bit)で構成されます。
基本データ型の値域は、その型が占める領域の大きさによって決まります。以下では Java を例に取ります。
基本データ型が取り得る値の範囲は、使う空間の大きさによって決まります。Java を例に見てみましょう。
- 整数型 `byte` は $1$ バイト = $8$ ビットを占め、$2^{8}$ 個の数を表せます。
- 整数型 `int` は $4$ バイト = $32$ ビットを占め、$2^{32}$ 個の数を表せます。
- 整数型 `byte` は $1$ バイト = $8$ ビットを使い、$2^{8}$ 個の数を表せます。
- 整数型 `int` は $4$ バイト = $32$ ビットを使い、$2^{32}$ 個の数を表せます。
下表は、Java における各種基本データ型の使用領域、値域、デフォルト値を示したものです。この表を丸暗記する必要はなく、大まかに理解しておけば十分であり、必要になったときに参照すればかまいません。
下表に、Java の基本データ型の使用量、値の範囲、デフォルト値を示します。暗記する必要はなく、大まかに理解し、必要なときに表を確認すれば十分です。
<p align="center"> 表 <id> &nbsp; 基本データ型の使用領域と値域 </p>
<p align="center"> 表 <id> &nbsp; 基本データ型の使用量と値の範囲 </p>
| 型 | 記号 | 使用領域 | 最小値 | 最大値 | デフォルト値 |
| 型 | 記号 | 使用量 | 最小値 | 最大値 | デフォルト値 |
| ------ | -------- | -------- | ------------------------ | ----------------------- | -------------- |
| 整数 | `byte` | 1 バイト | $-2^7$ ($-128$) | $2^7 - 1$ ($127$) | $0$ |
| | `short` | 2 バイト | $-2^{15}$ | $2^{15} - 1$ | $0$ |
| | `int` | 4 バイト | $-2^{31}$ | $2^{31} - 1$ | $0$ |
| | `long` | 8 バイト | $-2^{63}$ | $2^{63} - 1$ | $0$ |
| 浮動小数点数 | `float` | 4 バイト | $1.175 \times 10^{-38}$ | $3.403 \times 10^{38}$ | $0.0\text{f}$ |
| | `double` | 8 バイト | $2.225 \times 10^{-308}$ | $1.798 \times 10^{308}$ | $0.0$ |
| 文字 | `char` | 2 バイト | $0$ | $2^{16} - 1$ | $0$ |
| 真偽値 | `bool` | 1 バイト | $\text{false}$ | $\text{true}$ | $\text{false}$ |
| 整数 | `byte` | 1 バイト | $-2^7$ ($-128$) | $2^7 - 1$ ($127$) | $0$ |
| | `short` | 2 バイト | $-2^{15}$ | $2^{15} - 1$ | $0$ |
| | `int` | 4 バイト | $-2^{31}$ | $2^{31} - 1$ | $0$ |
| | `long` | 8 バイト | $-2^{63}$ | $2^{63} - 1$ | $0$ |
| 浮動小数点数 | `float` | 4 バイト | $1.175 \times 10^{-38}$ | $3.403 \times 10^{38}$ | $0.0\text{f}$ |
| | `double` | 8 バイト | $2.225 \times 10^{-308}$ | $1.798 \times 10^{308}$ | $0.0$ |
| 文字 | `char` | 2 バイト | $0$ | $2^{16} - 1$ | $0$ |
| ブール | `bool` | 1 バイト | $\text{false}$ | $\text{true}$ | $\text{false}$ |
注意してください。上表は Java の基本データ型に対するものです。各プログラミング言語にはそれぞれ独自のデータ型定義があり、使用領域、値域、デフォルト値は異なる場合があります。
この表は Java の基本データ型を対象にしています。言語ごとに型の定義があり、使用量、値の範囲、デフォルト値が異なる場合があります。
- Python では、整数型 `int` は利用可能なメモリに制限されるだけで任意の大きさを取れます。浮動小数点数 `float` は倍精度 64 ビットです。`char` 型はなく、1 文字は実際には長さ 1 の文字列 `str` です。
- C と C++ では基本データ型の大きさは明確に規定されておらず、実装やプラットフォームによって異なります。上表は LP64 [データモデル](https://en.cppreference.com/w/cpp/language/types#Properties) に従っており、Linux や macOS を含む Unix 系 64 ビット OS で用いられています。
- `char` の大きさは C と C++ では 1 バイトですが、多くのプログラミング言語では採用する文字エンコーディング方式によって決まります。詳しくは「文字エンコーディング」の章を参照してください。
- 真偽値を表すのに必要なのは 1 ビット($0$ または $1$)だけですが、メモリ上では通常 1 バイトとして格納されます。これは、現代のコンピュータ CPU が通常 1 バイトを最小のアドレス指定可能なメモリ単位としているためです。
- Python の整数型 `int` は、利用可能なメモリの範囲内で任意の大きさを扱えます。`float` は 64 ビットの倍精度で、`char` 型はなく、1 文字は長さ 1 の文字列 `str` です。
- C と C++ は基本データ型の大きさを厳密には定めておらず、実装や環境によって異なります。上表は、Linux や macOS を含む 64 ビット Unix 系 OS で使われる LP64 [データモデル](https://en.cppreference.com/w/cpp/language/types#Properties)に従っています。
- C と C++ の `char` は 1 バイトです。多くの言語では、文字の大きさは文字コード方式に依存します。詳しくは「文字エンコーディング」の節で説明します。
- ブール値は 1 ビット($0$ または $1$)で表せますが、メモリでは通常、1 バイトとして保存されます。現代の CPU が通常、1 バイトを最小のアドレス指定単位とするためです。
では、基本データ型とデータ構造の間にはどのような関係があるのでしょうか。データ構造とは、コンピュータ内でデータを組織し格納する方法のことです。この言葉で主役なのは「データ」ではなく「構造」です。
では、基本データ型とデータ構造はどう関係するのでしょうか。データ構造は、コンピュータ内でデータを整理・格納する方法です。この言葉の中心は「データ」ではなく「構造」にあります。
「数字の並び」を表したいなら、自然に配列の使用を思い浮かべるでしょう。これは、配列の線形構造が数字どうしの隣接関係や順序関係を表せるからです。しかし、格納する内容が整数 `int` なのか、小数 `float` なのか、文字 `char` なのかは、「データ構造」とは関係ありません。
「数が一列に並んだもの」を表すなら、自然に配列が思い浮かびます。配列の線形構造が、隣接関係や順序を表せるためです。格納するのが整数 `int`、小数 `float`、文字 `char` のどれかは、「データ構造」とは別のことです。
言い換えると、**基本データ型はデータの「内容の型」を提供し、データ構造はデータの「組織方法」を提供します**。たとえば次のコードでは、同じデータ構造(配列)を使って `int`、`float`、`char`、`bool` など異なる基本データ型を格納・表現しています。
つまり、**基本データ型はデータの「内容の型」を、データ構造は「整理の仕方」を提供します**。次のコードでは、同じデータ構造(配列)を使い、`int`、`float`、`char`、`bool` など、異なる基本データ型を格納・表現します。
=== "Python"
```python title=""
# さまざまな基本データ型で配列を初期化する
# さまざまな基本データ型で配列を初期化
numbers: list[int] = [0] * 5
decimals: list[float] = [0.0] * 5
# Python の文字は実際には長さ 1 の文字列
# Python の文字は、実際には長さ 1 の文字列
characters: list[str] = ['0'] * 5
bools: list[bool] = [False] * 5
# Python のリストはさまざまな基本データ型とオブジェクト参照を自由に格納できる
# Python のリストには、さまざまな基本データ型やオブジェクトへの参照を自由に格納できる
data = [0, 0.0, 'a', False, ListNode(0)]
```
=== "C++"
```cpp title=""
// さまざまな基本データ型で配列を初期化する
// さまざまな基本データ型で配列を初期化
int numbers[5];
float decimals[5];
char characters[5];
@@ -70,7 +70,7 @@
=== "Java"
```java title=""
// さまざまな基本データ型で配列を初期化する
// さまざまな基本データ型で配列を初期化
int[] numbers = new int[5];
float[] decimals = new float[5];
char[] characters = new char[5];
@@ -80,7 +80,7 @@
=== "C#"
```csharp title=""
// さまざまな基本データ型で配列を初期化する
// さまざまな基本データ型で配列を初期化
int[] numbers = new int[5];
float[] decimals = new float[5];
char[] characters = new char[5];
@@ -90,7 +90,7 @@
=== "Go"
```go title=""
// さまざまな基本データ型で配列を初期化する
// さまざまな基本データ型で配列を初期化
var numbers = [5]int{}
var decimals = [5]float64{}
var characters = [5]byte{}
@@ -100,7 +100,7 @@
=== "Swift"
```swift title=""
// さまざまな基本データ型で配列を初期化する
// さまざまな基本データ型で配列を初期化
let numbers = Array(repeating: 0, count: 5)
let decimals = Array(repeating: 0.0, count: 5)
let characters: [Character] = Array(repeating: "a", count: 5)
@@ -110,14 +110,14 @@
=== "JS"
```javascript title=""
// JavaScript の配列はさまざまな基本データ型とオブジェクトを自由に格納できる
// JavaScript の配列には、さまざまな基本データ型やオブジェクトを自由に格納できる
const array = [0, 0.0, 'a', false];
```
=== "TS"
```typescript title=""
// さまざまな基本データ型で配列を初期化する
// さまざまな基本データ型で配列を初期化
const numbers: number[] = [];
const characters: string[] = [];
const bools: boolean[] = [];
@@ -126,7 +126,7 @@
=== "Dart"
```dart title=""
// さまざまな基本データ型で配列を初期化する
// さまざまな基本データ型で配列を初期化
List<int> numbers = List.filled(5, 0);
List<double> decimals = List.filled(5, 0.0);
List<String> characters = List.filled(5, 'a');
@@ -136,7 +136,7 @@
=== "Rust"
```rust title=""
// さまざまな基本データ型で配列を初期化する
// さまざまな基本データ型で配列を初期化
let numbers: Vec<i32> = vec![0; 5];
let decimals: Vec<f32> = vec![0.0; 5];
let characters: Vec<char> = vec!['0'; 5];
@@ -146,7 +146,7 @@
=== "C"
```c title=""
// さまざまな基本データ型で配列を初期化する
// さまざまな基本データ型で配列を初期化
int numbers[10];
float decimals[10];
char characters[10];
@@ -156,7 +156,7 @@
=== "Kotlin"
```kotlin title=""
// さまざまな基本データ型で配列を初期化する
// さまざまな基本データ型で配列を初期化
val numbers = IntArray(5)
val decinals = FloatArray(5)
val characters = CharArray(5)
@@ -166,10 +166,10 @@
=== "Ruby"
```ruby title=""
# Ruby のリストはさまざまな基本データ型とオブジェクト参照を自由に格納できる
# Ruby のリストには、さまざまな基本データ型やオブジェクトへの参照を自由に格納できる
data = [0, 0.0, 'a', false, ListNode(0)]
```
??? pythontutor "実行の可視化"
https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%93%BE%E8%A1%A8%E8%8A%82%E7%82%B9%E7%B1%BB%22%22%22%0A%20%20%20%20def%20__init__%28self,%20val%3A%20int%29%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E8%8A%82%E7%82%B9%E5%80%BC%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E5%90%8E%E7%BB%A7%E8%8A%82%E7%82%B9%E5%BC%95%E7%94%A8%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E4%BD%BF%E7%94%A8%E5%A4%9A%E7%A7%8D%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E6%9D%A5%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E7%BB%84%0A%20%20%20%20numbers%20%3D%20%5B0%5D%20*%205%0A%20%20%20%20decimals%20%3D%20%5B0.0%5D%20*%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E5%AD%97%E7%AC%A6%E5%AE%9E%E9%99%85%E4%B8%8A%E6%98%AF%E9%95%BF%E5%BA%A6%E4%B8%BA%201%20%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%0A%20%20%20%20characters%20%3D%20%5B'0'%5D%20*%205%0A%20%20%20%20bools%20%3D%20%5BFalse%5D%20*%205%0A%20%20%20%20%23%20Python%20%E7%9A%84%E5%88%97%E8%A1%A8%E5%8F%AF%E4%BB%A5%E8%87%AA%E7%94%B1%E5%AD%98%E5%82%A8%E5%90%84%E7%A7%8D%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%92%8C%E5%AF%B9%E8%B1%A1%E5%BC%95%E7%94%A8%0A%20%20%20%20data%20%3D%20%5B0,%200.0,%20'a',%20False,%20ListNode%280%29%5D&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false
https://pythontutor.com/render.html#code=class%20ListNode%3A%0A%20%20%20%20%22%22%22%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%82%AF%E3%83%A9%E3%82%B9%22%22%22%0A%20%20%20%20def%20__init__(self%2C%20val%3A%20int)%3A%0A%20%20%20%20%20%20%20%20self.val%3A%20int%20%3D%20val%20%20%23%20%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E5%80%A4%0A%20%20%20%20%20%20%20%20self.next%3A%20ListNode%20%7C%20None%20%3D%20None%20%20%23%20%E6%AC%A1%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%0A%0A%22%22%22%E5%AE%9F%E8%A1%8C%E7%94%A8%E3%82%B3%E3%83%BC%E3%83%89%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%23%20%E3%81%95%E3%81%BE%E3%81%96%E3%81%BE%E3%81%AA%E5%9F%BA%E6%9C%AC%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B%E3%81%A7%E9%85%8D%E5%88%97%E3%82%92%E5%88%9D%E6%9C%9F%E5%8C%96%0A%20%20%20%20numbers%20%3D%20%5B0%5D%20*%205%0A%20%20%20%20decimals%20%3D%20%5B0.0%5D%20*%205%0A%20%20%20%20%23%20Python%20%E3%81%AE%E6%96%87%E5%AD%97%E3%81%AF%E3%80%81%E5%AE%9F%E9%9A%9B%E3%81%AB%E3%81%AF%E9%95%B7%E3%81%95%201%20%E3%81%AE%E6%96%87%E5%AD%97%E5%88%97%0A%20%20%20%20characters%20%3D%20%5B'0'%5D%20*%205%0A%20%20%20%20bools%20%3D%20%5BFalse%5D%20*%205%0A%20%20%20%20%23%20Python%20%E3%81%AE%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AB%E3%81%AF%E3%80%81%E3%81%95%E3%81%BE%E3%81%96%E3%81%BE%E3%81%AA%E5%9F%BA%E6%9C%AC%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B%E3%82%84%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%B8%E3%81%AE%E5%8F%82%E7%85%A7%E3%82%92%E8%87%AA%E7%94%B1%E3%81%AB%E6%A0%BC%E7%B4%8D%E3%81%A7%E3%81%8D%E3%82%8B%0A%20%20%20%20data%20%3D%20%5B0%2C%200.0%2C%20'a'%2C%20False%2C%20ListNode(0)%5D&cumulative=false&curInstr=12&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -1,85 +1,85 @@
# 文字エンコーディング *
コンピュータでは、すべてのデータは二進数の形で保存されており、文字 `char` も例外ではありません。文字を表すためには、「文字セット」を定義し、各文字と二進数の間の一対一の対応関係を定める必要があります。文字セットがあれば、コンピュータは対応表を参照して二進数から文字への変換を行えます。
コンピュータでは、すべてのデータが二進数の形で保存されており、文字 `char` も例外ではありません。文字を表すには、それぞれの文字と二進数の一対一の対応を定めた「文字集合」を用意する必要があります。文字集合があれば、コンピュータは対応表を参照して二進数を文字に変換できます。
## ASCII 文字セット
## ASCII 文字集合
<u>ASCII コード</u>は最も早く登場した文字セットで、その正式名称は American Standard Code for Information Interchange(米国標準情報交換コード)です。これは 7 ビットの二進数(1 バイトの下位 7 ビット)で 1 文字を表し、最大で 128 種類の異なる文字を表現できます。下図のように、ASCII コードには英字の大文字と小文字、数字 0 ~ 9、いくつかの句読点、そしていくつかの制御文字(改行やタブなど)が含まれます。
<u>ASCII コード</u>は最初期の文字集合で、正式名称は American Standard Code for Information Interchange(情報交換用米国標準コード)です。7 ビットの二進数(1 バイトの下位 7 ビット)で 1 文字を表し、最大 128 種類の文字を表現できます。下図のように、ASCII コードには英字の大文字と小文字、数字の 0 ~ 9、一部の句読点や記号、改行やタブなどの制御文字が含まれています。
![ASCII コード](character_encoding.assets/ascii_table.png)
しかし、**ASCII コードで表現できるのは英語だけです**。コンピュータのグローバル化に伴い、より多くの言語を表せる <u>EASCII</u> 文字セットが生まれました。これは ASCII の 7 ビットを 8 ビットへ拡張したもので、256 種類の異なる文字を表現できます。
しかし、**ASCII コードで表せるのは英語だけです**。コンピュータの利用が世界に広がるにつれて、より多くの言語を表現できる <u>EASCII</u> 文字集合が生まれました。ASCII の 7 ビットを 8 ビットに拡張したもので、256 種類の文字を表現できます。
世界では、さまざまな地域に適した EASCII 文字セットが次々に登場しました。これらの文字セットでは、前半の 128 文字は ASCII コードで統一され、後半の 128 文字は各言語の要件に合わせて個別に定義されています。
世界各地では、それぞれの地域に適した EASCII 文字集合が次々に登場しました。いずれも先頭の 128 文字は ASCII コードと共通ですが、後半の 128 文字は各言語の需要に合わせて異なる定義になっています。
## GBK 文字セット
## GBK 文字集合
その後、人々は**EASCII コードでも多くの言語に必要な文字数を満たせない**ことに気づきました。たとえば漢字は 10 万字近くあり、日常的に使うものだけでも数千字あります。中国国家標準総局は 1980 年に <u>GB2312</u> 文字セットを公開し、6763 字の漢字を収録して、漢字のコンピュータ処理の基本的な需要を満たしました。
その後、**EASCII コードでも、多くの言語が必要とする文字数には対応できない**ことがわかりました。例えば漢字は 10 万字近くあり、日常的に使うものだけでも数千字に上ります。中国の国家標準総局は 1980 年に <u>GB2312</u> 文字集合を発表しました。6763 字の漢字を収録し、コンピュータによる漢字処理の基本的な需要を満たすものでした。
しかし、GB2312 では一部の珍しい字や繁体字を扱えません。<u>GBK</u> 文字セットは GB2312 を基に拡張されたもので、合計 21886 字の漢字を収録しています。GBK のエンコーディング方式では、ASCII 文字は 1 バイト、漢字は 2 バイトで表されます。
ただし、GB2312 では一部のまれな漢字や繁体字を扱えません。<u>GBK</u> 文字集合は GB2312 を拡張したもので、合計 21886 字の漢字を収録しています。GBK の符号化方式では、ASCII 文字を 1 バイト、漢字を 2 バイトで表します。
## Unicode 文字セット
## Unicode 文字集合
コンピュータ技術が急速に発展するにつれて、文字セットと符号化規格は百花繚乱の状態となり、それに伴って多くの問題も生じました。一方では、これらの文字セットは通常、特定の言語の文字しか定義しておらず、多言語環境では正常に動作できませんでした。もう一方では、同じ言語にも複数の文字セット規格が存在し、2 台のコンピュータが異なる符号化規格を使っていると、情報伝達の際に文字化けが発生しました。
コンピュータ技術の急速な発展とともに、多種多様な文字集合や符号化規格が生まれましたが、それに伴って多くの問題も生じました。一つは、これらの文字集合が通常は特定の言語の文字しか定義しておらず、多言語環境では正しく動作しないことです。もう一つは、同じ言語にも複数の文字集合の規格があり、2 台のコンピュータが異なる符号化規格を使っていると、情報のやり取りで文字化けが起こることです。
当時の研究者たちはこう考えました。**十分に完全な文字セットを打ち出して、世界中のあらゆる言語と記号をそこに収録すれば、多言語環境や文字化けの問題を解決できるのではないか**。この発想に後押しされて、大規模で包括的な文字セット Unicode が誕生しました。
当時の研究者たちは、**世界中のあらゆる言語や記号を収録する、十分に網羅的な文字集合を作れば、多言語環境や文字化けの問題を解決できるのではないか**と考えました。この発想から、大規模で網羅的な文字集合 Unicode が誕生しました。
<u>Unicode</u> の中国語名は「統一コード」であり、理論上は 100 万を超える文字を収容できます。Unicode は世界中の文字を 1 つの文字セットに統合することを目指し、さまざまな言語の文字を処理・表示できる汎用文字セットを提供することで、符号化規格の違いによる文字化けを減らそうとしています。1991 年の公開以来、Unicode は新しい言語と文字を継続的に拡充してきました。2022 年 9 月時点で、Unicode にはすでに 149186 文字が含まれており、各種言語の文字、記号、さらには絵文字まで収録されています。
<u>Unicode</u> は中国語では「统一码」と呼ばれ、理論上は 100 万以上の文字を収容できます。世界中の文字を統一された文字集合に取り込み、さまざまな言語の文字を処理・表示するための共通の文字集合を提供することで、符号化規格の違いによる文字化けを減らすことを目指しています。1991 年の公開以来、新しい言語や文字が継続的に追加されています。2022 年 9 月時点では、各言語の文字、記号、さらには絵文字など、149186 文字が収録されています。
Unicode は汎用文字セットとして、本質的には各文字に固有の「コードポイント」(文字番号)を割り当てており、その範囲は U+0000 から U+10FFFF までで、統一された文字番号空間を構成しています。しかし、**Unicode はそれらのコードポイントをコンピュータ内でどのように保存するかまでは規定していません**。ここで疑問が生じます。長さの異なる Unicode コードポイントが同じテキストに現れたとき、システムはどのように文字を解析するのでしょうか。たとえば長さ 2 バイトの符号が与えられたとき、それが 2 バイトの 1 文字なのか、1 バイトの 2 文字なのかをどう判定するのでしょうか。
共通の文字集合である Unicode は、本質的には各文字に一意の「コードポイント」(文字番号)を割り当てるものです。その範囲は U+0000 から U+10FFFF で、統一された文字番号の空間を構成しています。ただし、**Unicode はこれらの文字のコードポイントをコンピュータにどのように保存するかを定めていません**。ここで、長さの異なる Unicode コードポイントが一つのテキストに混在するとき、システムはどのように文字を読み取るのかという疑問が生じます。例えば、長さが 2 バイトの符号が与えられたとき、それが 2 バイトの 1 文字なのか、1 バイトの 2 文字なのかを、どう判断するのでしょうか。
この問題に対して、**すべての文字を固定長の符号として保存する**という直接的な解決策があります。下図のように、「Hello」の各文字は 1 バイト、「アルゴリズム」の各文字は 2 バイトを占めます。上位ビットを 0 で埋めることで、「Hello アルゴリズム」のすべての文字を 2 バイト長にエンコードできます。こうすれば、システムは 2 バイトごとに 1 文字を解析して、この語句の内容を復元できます。
この問題に対する**直接的な解決策は、すべての文字を同じ長さの符号で保存することです**。下図では、「Hello」の各文字は 1 バイト、「算法」の各文字は 2 バイトを占めています。上位ビットを 0 で埋めれば、「Hello 算法」のすべての文字を 2 バイトの長さに符号化できます。これにより、システムは 2 バイトごとに 1 文字を読み取り、元の語句を復元できます。
![Unicode エンコーディングの例](character_encoding.assets/unicode_hello_algo.png)
![Unicode の符号化例](character_encoding.assets/unicode_hello_algo.png)
しかし ASCII コードはすでに、英語の符号化には 1 バイトで十分であることを示しています。上記の方式を採用すると、英語のテキストが占める空間は ASCII エンコーディング時の 2 倍になり、メモリ空間の浪費が大きくなります。そのため、より効率的な Unicode エンコーディング方式が必要です。
しかし、ASCII コードからわかるように、英語の符号化に必要なのは 1 バイトだけです。上記の方式では、英語のテキストが占める容量は ASCII の場合の 2 倍となり、多くのメモリを無駄にします。そのため、より効率的な Unicode の符号化方式が必要です。
## UTF-8 エンコーディング
現在、UTF-8 は国際的に最も広く使われている Unicode エンコーディング方式になっています。**これは可変長のエンコーディング**であり、1 文字を 1 〜 4 バイトで表し、文字の複雑さに応じて長さが変わります。ASCII 文字は 1 バイト、ラテン文字とギリシャ文字は 2 バイト、一般的な漢字は 3 バイト、そのほかの一部の珍しい文字は 4 バイト必要です。
現在、UTF-8 は世界で最も広く使われている Unicode の符号化方式です。**可変長の符号化方式であり**、文字の複雑さに応じて 1 文字を 1 ~ 4 バイトで表します。ASCII 文字は 1 バイト、ラテン文字やギリシャ文字は 2 バイト、よく使われる中国語の文字は 3 バイト、その他の一部のまれな文字は 4 バイトを必要とします。
UTF-8 の符号化規則はそれほど複雑ではなく、次の 2 つのケースに分けられます。
UTF-8 の符号化規則は複雑ではなく、次の二つの場合に分かれます。
- 長さ 1 バイトの文字では、最上位ビットを $0$ にし、残りの 7 ビットを Unicode コードポイントに設定します。ここで注意すべきなのは、ASCII 文字が Unicode 文字セットの先頭 128 個のコードポイントを占めていることです。つまり、**UTF-8 エンコーディングは ASCII コードと下位互換性があります**。このため、UTF-8 を使って古い ASCII コードのテキストを解析できます。
- 長さ $n$ バイトの文字(ただし $n > 1$)では、先頭バイトの上位 $n$ ビットをすべて $1$ にし、第 $n + 1$ ビットを $0$ に設定します。2 バイト目以降では、各バイトの上位 2 ビットをいずれも $10$ にし、残りのすべてのビットで文字の Unicode コードポイントを埋めます。
- 長さが 1 バイトの文字では、最上位ビットを $0$ にし、残りの 7 ビットに Unicode コードポイントを設定します。ASCII 文字は Unicode 文字集合の先頭の 128 コードポイントを占めています。つまり、**UTF-8 は ASCII コードとの後方互換性を備えています**。そのため、古い ASCII のテキストも UTF-8 として読み取れます。
- 長さが $n$ バイトの文字($n > 1$)では、最初のバイトの上位 $n$ ビットをすべて $1$ にし、上位から $n + 1$ 番目のビットを $0$ にします。2 バイト目以降は各バイトの上位 2 ビットを $10$ にし、残りのすべてのビットに文字の Unicode コードポイントを埋め込みます。
下図は「Helloアルゴリズム」に対応する UTF-8 エンコーディングを示しています。観察すると、上位 $n$ ビットがすべて $1$ に設定されているため、システムは先頭から連続する $1$ の個数を読むことで、その文字の長さが $n$ であると解析できます。
下図は「Hello算法」に対応する UTF-8 の符号化を示しています。上位 $n$ ビットがすべて $1$ なので、システムは先頭から連続する $1$ の数を読み取ることで、文字の長さが $n$ バイトであると判断できます。
では、なぜ残りのすべてのバイトの上位 2 ビットを $10$ にするのでしょうか。実は、この $10$ は検査用の印として機能します。システムが誤ったバイト位置からテキストを解析し始めたとしても、バイト先頭の $10$ によって異常を素早く判定できます。
では、後続のすべてのバイトの上位 2 ビットを $10$ にするのはなぜでしょうか。実は、この $10$ は検査用の印として機能します。誤ったバイト位置からテキストを読み取り始めた場合でも、バイトの先頭にある $10$ によって、システムはすぐに異常を検出できます。
この $10$ を検査用の印とする理由は、UTF-8 の符号化規則では上位 2 ビットが $10$ になる文字は存在しないからです。この結論は背理法で証明できます。ある文字の上位 2 ビットが $10$ だと仮定すると、その文字の長さは $1$ であり、ASCII コードに対応することになります。しかし ASCII コードの最上位ビットは $0$ であるはずなので、仮定と矛盾します。
$10$ を検査用の印にできるのは、UTF-8 の符号化規則では、文字の先頭の 2 ビットが $10$ になることはないからです。これは背理法で証明できます。ある文字の先頭の 2 ビットが $10$ だと仮定すると、その文字の長さは $1$ バイトであり、ASCII コードに対応することになります。しかし、ASCII コードの最上位ビットは $0$ であるはずなので、仮定と矛盾します。
![UTF-8 エンコーディングの例](character_encoding.assets/utf-8_hello_algo.png)
![UTF-8 の符号化例](character_encoding.assets/utf-8_hello_algo.png)
UTF-8 以外にも、一般的なエンコーディング方式として次の 2 つがあります。
UTF-8 のほかにも、次の二つの符号化方式がよく使われます。
- **UTF-16 エンコーディング**:1 文字を 2 バイトまたは 4 バイトで表します。すべての ASCII 文字と一般的な非英語文字は 2 バイトで表し、一部の文字だけが 4 バイトを必要とします。2 バイトの文字については、UTF-16 エンコーディングは Unicode コードポイントと等しくなります。
- **UTF-32 エンコーディング**:各文字を必ず 4 バイトで表します。つまり UTF-32 は UTF-8 や UTF-16 よりも多くの領域を消費し、とくに ASCII 文字の比率が高いテキストでその傾向が顕著です。
- **UTF-16 エンコーディング**:1 文字を 2 バイトまたは 4 バイトで表します。すべての ASCII 文字と、よく使われる英語以外の文字は 2 バイトで表し、一部の文字には 4 バイトが必要です。2 バイトで表す文字では、UTF-16 の符号値は Unicode コードポイントと等しくなります。
- **UTF-32 エンコーディング**:すべての文字を 4 バイトで表します。そのため、UTF-32 は UTF-8 や UTF-16 より多くの容量を使い、特に ASCII 文字の割合が高いテキストではその差が大きくなります。
記憶領域の使用量という観点では、UTF-8 は英語文字の表現に非常に効率的で、必要なのは 1 バイトだけです。一方、UTF-16 は一部の非英語文字(たとえば中国語の文字)の符号化でより効率的になることがあり、必要なのは 2 バイトだけで、UTF-8 では 3 バイト必要になる場合があります。
保存容量の面では、UTF-8 は英語の文字をわずか 1 バイトで表せるため、非常に効率的です。一方、中国語などの一部の英語以外の文字は、UTF-16 なら 2 バイトで済むのに対し、UTF-8 では 3 バイト必要になることがあるため、UTF-16 の方が効率的です。
互換性という観点では、UTF-8 の汎用性が最も高く、多くのツールやライブラリが UTF-8 を優先的にサポートしています。
互換性の面では、UTF-8 が最も汎用性が高く、多くのツールやライブラリが UTF-8 を優先的にサポートしています。
## プログラミング言語の文字エンコーディング
従来の多くのプログラミング言語では、実行中の文字列に UTF-16 や UTF-32 のような固定長エンコーディングが使われています。固定長エンコーディングでは、文字列を配列のように扱えるため、次のような利点があります。
従来の多くのプログラミング言語では、プログラム実行中の文字列に、UTF-16 や UTF-32 のような固定長の符号化方式を使っていました。固定長なら文字列を配列として扱うことができ、次のような利点があります。
- **ランダムアクセス**:UTF-16 で符号化された文字列はランダムアクセスが容易です。UTF-8 は可変長エンコーディングなので、第 $i$ 文字を見つけるには文字列の先頭から第 $i$ 文字まで走査する必要があり、$O(n)$ の時間がかかります。
- **文字数の計算**:ランダムアクセスと同様に、UTF-16 で符号化された文字列の長さを計算するのも $O(1)$ の操作です。しかし、UTF-8 で符号化された文字列の長さを計算するには、文字列全体を走査する必要があります。
- **文字列操作**:UTF-16 で符号化された文字列では、多くの文字列操作(分割、連結、挿入、削除など)をより簡単に行えます。UTF-8 で符号化された文字列では、これらの操作を行う際に、無効な UTF-8 エンコーディングを生じさせないための追加計算が通常必要になります。
- **ランダムアクセス**:UTF-16 で符号化された文字列には、容易にランダムアクセスできます。UTF-8 は可変長なので、第 $i$ 文字を見つけるには、文字列の先頭から第 $i$ 文字まで走査する必要があり、$O(n)$ の時間がかかります。
- **文字数の取得**:ランダムアクセスと同様に、UTF-16 で符号化された文字列の長さを求める操作も $O(1)$ です。一方、UTF-8 で符号化された文字列の長さを求めるには、文字列全体を走査する必要があります。
- **文字列操作**:UTF-16 で符号化された文字列では、分割、連結、挿入、削除など、多くの操作を行いやすくなります。UTF-8 で符号化された文字列でこれらの操作を行う場合は、不正な UTF-8 の符号を生じさせないようにするため、通常は追加の計算が必要です。
実際、プログラミング言語における文字エンコーディング方式の設計は、とても興味深い話題であり、多くの要因が関わっています。
実際、プログラミング言語の文字エンコーディングの設計は、さまざまな要因が関わる興味深いテーマです。
- Java の `String` 型は UTF-16 エンコーディングを使用し、各文字は 2 バイトを占めます。これは Java 言語の設計当初、人々が 16 ビットあればあらゆる文字を表現するのに十分だと考えていたためです。しかし、これは誤った判断でした。その後 Unicode 規格は 16 ビットを超える範囲へ拡張されたため、現在の Java では 1 文字が 16 ビット値の組(「サロゲートペア」)で表されることがあります。
- JavaScript と TypeScript の文字列が UTF-16 エンコーディングを使う理由も Java と似ています。1995 年に Netscape 社が初めて JavaScript 言語を公開した当時、Unicode はまだ発展初期にあり、16 ビットの符号化で十分すべての Unicode 文字を表せると考えられていました。
- C# が UTF-16 エンコーディングを使う主な理由は、.NET プラットフォームが Microsoft によって設計され、Microsoft の多くの技術(Windows オペレーティングシステムを含む)で UTF-16 エンコーディングが広く使われているためです。
- Java の `String` 型は UTF-16 を使い、各文字が 2 バイトを占めます。これは、Java の設計当初、16 ビットであらゆる文字を表現できると考えられていたためです。しかし、その判断は誤りでした。その後、Unicode の規格が 16 ビットの範囲を超えて拡張されたため、現在の Java では、文字が「サロゲートペア」と呼ばれる二つの 16 ビット値の組で表されることがあります。
- JavaScript と TypeScript の文字列が UTF-16 を使う理由も Java と同様です。Netscape が 1995 年に JavaScript を初めて公開したとき、Unicode はまだ発展の初期段階にあり、16 ビットの符号ですべての Unicode 文字を表現できました。
- C# が UTF-16 を使う主な理由は、.NET プラットフォームを設計した Microsoft の多くの技術で、Windows を含めて UTF-16 が広く使われているためです。
以上のプログラミング言語は文字数を過小評価していたため、16 ビットを超える長さの Unicode 文字を表すために「サロゲートペア」を採用せざるを得ませんでした。これはやむを得ない妥協策です。一方では、サロゲートペアを含む文字列では、1 文字が 2 バイトまたは 4 バイトを占める可能性があり、固定長エンコーディングの利点が失われます。もう一方では、サロゲートペアの処理には追加のコードが必要となり、プログラミングの複雑さとデバッグの難しさが増します。
これらのプログラミング言語は文字数を少なく見積もったため、16 ビットを超える Unicode 文字を「サロゲートペア」で表さざるを得なくなりました。これはやむを得ない対応でした。一方では、サロゲートペアを含む文字列では 1 文字が 2 バイトにも 4 バイトにもなり、固定長という利点が失われます。他方では、サロゲートペアを扱うために追加のコードが必要となり、プログラミングが複雑になってデバッグも難しくなります。
こうした理由から、一部のプログラミング言語では別のエンコーディング方式が採用されました。
こうした理由から、異なる符号化方式を採用しているプログラミング言語もあります。
- Python の `str` は Unicode エンコーディングを使用し、柔軟な文字列表現を採用しています。保存される文字の長さは、その文字列中で最大の Unicode コードポイントに依存します。文字列がすべて ASCII 文字であれば各文字は 1 バイト、ASCII の範囲を超える文字があってもすべてが基本多言語面(BMP)内であれば各文字は 2 バイト、BMP を超える文字があれば各文字は 4 バイトを占めます。
- Go 言語の `string` 型は内部で UTF-8 エンコーディングを使用します。Go 言語には単一の Unicode コードポイントを表す `rune` 型も用意されています。
- Rust 言語の `str` と `String` 型は内部で UTF-8 エンコーディングを使用します。Rust にも単一の Unicode コードポイントを表す `char` 型があります。
- Python の `str` は Unicode を使い、柔軟な文字列表現を採用しています。保存時の文字の長さは、文字列中の最大の Unicode コードポイントによって決まります。すべてが ASCII 文字なら各文字は 1 バイト、ASCII の範囲外の文字があってもすべてが基本多言語面(BMP)内なら各文字は 2 バイト、BMP の範囲外の文字がある場合は各文字が 4 バイトを占めます。
- Go の `string` 型は、内部で UTF-8 を使います。また、単一の Unicode コードポイントを表す `rune` 型も用意されています。
- Rust の `str` 型と `String` 型は、内部で UTF-8 を使います。また、単一の Unicode コードポイントを表す `char` 型も用意されています。
注意すべきなのは、ここまでの議論はすべて、プログラミング言語内での文字列の保存方法についてであり、**文字列をファイルに保存したりネットワークで転送したりする方法とは別の問題である**ということです。ファイル保存やネットワーク転送では、通常、互換性と空間効率を最適化するために文字列を UTF-8 形式にエンコードします。
ここまでの説明は、いずれもプログラミング言語内部での文字列の保存方法に関するものです。**ファイルへの保存方法やネットワークでの転送方法とは、別の問題である**ことに注意してください。ファイルに保存したりネットワークで転送したりするときは、通常、互換性と容量効率を最適にするために、文字列を UTF-8 形式に符号化します。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -1,48 +1,48 @@
# データ構造の分類
代表的なデータ構造には、配列、連結リスト、スタック、キュー、ハッシュテーブル、木、ヒープ、グラフがあり、これらは「論理構造」と「物理構造」の 2 つの観点から分類できます。
代表的なデータ構造には、配列、連結リスト、スタック、キュー、ハッシュテーブル、木、ヒープ、グラフがあります。これらは「論理構造」と「物理構造」の二つの観点で分類できます。
## 論理構造:線形と非線形
**論理構造はデータ要素間の論理的な関係を示します**。配列と連結リストでは、データは一定の順序で並び、データ間の線形関係を表します。一方、木ではデータは上から下へ階層的に並び、「祖先」と「子孫」の派生関係を示します。グラフはノードと辺で構成され、複雑なネットワーク関係を反映します。
**論理構造は、データ要素同士の論理的な関係を表します**。配列や連結リストでは一定の順に並び、線形の関係を表します。木では上から下へ階層的に並び、「祖先」から「子孫」へ派生する関係を表します。グラフはノードと辺からなり、複雑なネットワーク関係を表します。
以下の図に示すように、論理構造は「線形」と「非線形」の 2 つに大別できます。線形構造は比較的直感的で、データが論理関係において線形に並ぶことを指します。非線形構造はその逆で、非線形に配置されます。
下図のように、論理構造は「線形」と「非線形」に大別できます。線形構造は直感的に理解しやすく、要素が論理的に一列に並びます。非線形構造では、そのような一列の並びにはなりません。
- **線形データ構造**:配列、連結リスト、スタック、キュー、ハッシュテーブルであり、要素間は 1 対 1 の順序関係です。
- **線形データ構造**:配列、連結リスト、スタック、キュー、ハッシュテーブル。要素間には一対一の順序関係があります。
- **非線形データ構造**:木、ヒープ、グラフ、ハッシュテーブル。
非線形データ構造は、さらに木構造と網状構造に分けられます。
非線形データ構造は、さらに木構造とネットワーク構造に分けられます。
- **木構造**:木、ヒープ、ハッシュテーブルであり、要素間は 1 対多の関係です。
- **網状構造**:グラフであり、要素間は多対多の関係です。
- **木構造**:木、ヒープ、ハッシュテーブル。要素間は一対多の関係です。
- **ネットワーク構造**:グラフ。要素間は多対多の関係です。
![線形データ構造と非線形データ構造](classification_of_data_structure.assets/classification_logic_structure.png)
## 物理構造:連続と分散
**アルゴリズムのプログラムが実行されるとき、処理中のデータは主にメモリに格納されます**。下図はコンピュータのメモリモジュールを示しており、各黒い四角はそれぞれ 1 つのメモリ空間を表しています。メモリは巨大な Excel の表のようなものだと考えることができ、各セルには一定量のデータを格納できます。
**アルゴリズムの実行中、処理対象のデータは主にメモリに保存されます**。下図はメモリモジュールで、黒いチップのそれぞれにメモリ空間があります。メモリは巨大な Excel の表のように考えられ、各セルに一定量のデータを格納できます。
**システムはメモリアドレスを通じて目的の位置にあるデータへアクセスします**。下図に示すように、コンピュータは特定の規則に従って表内の各セルに番号を割り当て、各メモリ空間が一意のメモリアドレスを持つようにします。これらのアドレスがあれば、プログラムはメモリ内のデータにアクセスできます。
**システムはメモリアドレスを使って、目的の位置のデータにアクセスします**。下図のように、各セルには規則に従った番号が割り当てられ、各メモリ領域が一意のアドレスを持ちます。このアドレスにより、プログラムはデータにアクセスできます。
![メモリモジュール、メモリ空間、メモリアドレス](classification_of_data_structure.assets/computer_memory_location.png)
!!! tip
補足すると、メモリを Excel の表にたとえるのは単純化した比喩であり、実際のメモリの動作機構はより複雑で、アドレス空間、メモリ管理、キャッシュ機構、仮想メモリ、物理メモリなどの概念が関わります。
Excel の表は単純化したたとえです。実際のメモリの仕組みはもっと複雑で、アドレス空間、メモリ管理、キャッシュ、仮想メモリ、物理メモリなどが関わります。
メモリはすべてのプログラムで共有される資源であり、あるメモリ領域が 1 つのプログラムに占有されると、通常は他のプログラムが同時に利用できません。**したがって、データ構造とアルゴリズムの設計では、メモリ資源は重要な考慮要素です**。たとえば、アルゴリズムが使用するメモリ使用量のピークは、システムに残っている空きメモリを超えてはなりません。大きな連続メモリ領域が不足している場合、選択するデータ構造は分散したメモリ空間に格納できる必要があります。
メモリはすべてのプログラムが利用する資源ですが、ある領域を一つのプログラムが使っていると、通常はほかのプログラムが同時に使うことはできません。**そのため、データ構造とアルゴリズムの設計では、メモリ資源が重要な検討事項です**。たとえば最大使用量がシステムの空きメモリを超えないようにし、大きな連続領域がなければ、分散した領域に格納できるデータ構造を選ぶ必要があります。
下図に示すように、**物理構造はデータがコンピュータメモリ内にどのように格納されるかを表します**。これは連続空間への格納(配列)と分散空間への格納(連結リスト)に分けられます。物理構造は低レベルでデータのアクセス、更新、追加、削除などの操作方法を決定し、2 種類の物理構造は時間効率と空間効率の面で相補的な特徴を持ちます。
下図のように、**物理構造は、コンピュータのメモリ内での格納方法を表し**、連続した領域への格納(配列)と、分散した領域への格納(連結リスト)に分けられます。アクセス、更新、追加、削除などの方法を根本から決めるもので、二つの構造は時間効率と空間効率の面で互いを補い合います。
![連続空間格納と分散空間格納](classification_of_data_structure.assets/classification_phisical_structure.png)
![連続した領域への格納と分散した領域への格納](classification_of_data_structure.assets/classification_phisical_structure.png)
補足すると、**すべてのデータ構造は配列、連結リスト、またはその両者の組み合わせに基づいて実装されます**。たとえば、スタックとキューは配列でも連結リストでも実装できます。一方、ハッシュテーブルの実装には配列と連結リストの両方が含まれる場合があります。
**すべてのデータ構造は、配列、連結リスト、またはその組み合わせで実装されます**。たとえばスタックとキューはどちらでも実装でき、ハッシュテーブルでは両方を使うことがあります。
- **配列に基づいて実装可能**:スタック、キュー、ハッシュテーブル、木、ヒープ、グラフ、行列、テンソル(次元 $\geq 3$ の配列)など。
- **連結リストに基づいて実装可能**:スタック、キュー、ハッシュテーブル、木、ヒープ、グラフなど。
- **配列で実装できるもの**:スタック、キュー、ハッシュテーブル、木、ヒープ、グラフ、行列、テンソル(次元が $\geq 3$ の配列)など。
- **連結リストで実装できるもの**:スタック、キュー、ハッシュテーブル、木、ヒープ、グラフなど。
連結リストは初期化後も、プログラムの実行中に長さを調整できるため、「動的データ構造」とも呼ばれます。配列は初期化後に長さを変更できないため、「静的データ構造」とも呼ばれます。なお、配列もメモリを再割り当てすることで長さを変更でき、ある程度の「動的性」を持たせることができます。
連結リストは初期化後も実行中に長さを変えられるため、「動的データ構造」とも呼ばれます。配列は初期化後に長さを変えられないため、「静的データ構造」とも呼ばれます。ただし、配列もメモリを再確保すれば長さを変えられ、一定の「動的な性質」を持たせられます。
!!! tip
物理構造の理解が難しいと感じる場合は、先に次の章を読んでから本節を振り返ることを勧めます。
物理構造がわかりにくければ、次の章を読んでから、この節を振り返ってみてください。

View File

@@ -1,86 +1,86 @@
# 演習
# 練習問題
## 確認問題
## 知識の確認
### 身近な場面にあるデータの関係
### 日常の場面におけるデータの関係
データ同士の関係に基づいて、次の 3 つの場面に「線形構造、木構造、網状構造」のいずれかを選び、理由を説明してください。
データ同士の関係に基づき、次の各場面に当てはまるものを「線形構造、木構造、ネットワーク構造」から一つ選び、理由を説明してください。
<!-- numbered-subquestions -->
1. 生徒が一列に並び、それぞれが自分の前と後ろにいる人だけに注目する。
2. 学校が「学校 → 学年 → クラス」という階層で管理されている。
3. 都市の道路が複数の交差点を結び、1 つの交差点から複数の別の交差点へ行けるほか、道路が環状につながることもある。
1. 生徒たちが一列に並び、それぞれが自分の前と後ろの人だけを気にしています。
2. 学校が「学校 → 学年 → 学級」という階層で管理されています。
3. 市街地の道路が複数の交差点を結んでいます。一つの交差点から複数の別の交差点へ進め、環状の経路ができる場合もあります。
??? success "解答"
??? success "解答例"
1. 線形構造です。列の先頭と最後にいる人を除き、それぞれの人は前後の 1 人ずつとだけ隣り合い、関係が一本の線に沿って並んでいます。
1. 線形構造です。先頭と末尾以外の人は、前後に一人ずつ隣接し、関係が一本の線に沿って続きます。
2. 木構造です。各クラスはいずれか 1 つの学年に属し、各学年はさらに学校に属しており、関係が上から下へ階層的に広がっています。
2. 木構造です。各学級は一つの学年に属し、各学年は学校に属します。関係が上から下へ階層的に広がります。
3. 網状構造です。1 つの交差点を複数の交差点と結ぶことができ、経路が環状につながる場合もあるため、1 つの順序や厳密な階層では表せません。
3. ネットワーク構造です。一つの交差点が複数の交差点につながり、経路が環を作ることもあるため、単一の順序や厳密な階層には並べられません。
構造を判断するときは、メモリをどれだけ使うかを先に考えるのではなく、まず要素同士の関係を観察します。
構造を判断するときは、メモリ上の領域の大きさより先に、要素同士の関係を観察します。
### 論理的な順序をメモリに保存する方法
### 論理的な順序をメモリに格納するには
論理的な順序 `A → B → C` を保存するために、次の 2 つの簡略化したメモリ配置を考えます。
論理的な順序 `A → B → C` を保存するために、次の二つの簡略化したメモリ配置を考えます。
- 方法 A:`A、B、C` を、それぞれ番号 `20、21、22` のメモリ領域に置く。
- 方法 B:`A、B、C` を、それぞれ番号 `20、7、31` のメモリ領域に置き、`A` に `B` の位置、`B` に `C` の位置を記録する。
- 案 1:`A、B、C` を、それぞれ番号 `20、21、22` のメモリの区画に置きます。
- 案 2:`A、B、C` を、それぞれ番号 `20、7、31` のメモリの区画に置き、`A` に `B` の位置、`B` に `C` の位置を記録します。
<!-- numbered-subquestions -->
1. どちらが連続した領域への保存で、どちらが分散した領域への保存ですか?
2. 2 つの方法は、それぞれ配列と連結リストのどちらに近いですか?
3. 方法 B のメモリ領域の番号は大きさの順に並んでいません。それでも `A → B → C` という論理的な順序を表せるのはなぜですか?
1. どちらが連続した領域への格納で、どちらが分散した領域への格納ですか。
2. それぞれ、配列と連結リストのどちらに近いですか。
3. 案 2 では区画の番号が大小順になっていないのに、なぜ `A → B → C` の論理的な順序を表せるのでしょうか。
??? success "解答"
??? success "解答例"
1. 方法 A は連続したメモリ領域を使っているため、連続した領域への保存です。方法 B のノードは別々の場所に分散しているため、分散した領域への保存です。
1. 案 1 は連続したメモリの区画を使うため、連続した領域への格納です。案 2 はノードが別々の位置に分散するため、分散した領域への格納です。
2. 方法 A は配列に、方法 B は連結リストに近いです。
2. 案 1 は配列、案 2 は連結リストに近い方式です。
3. 論理的な順序を決めるのはノード間に記録された接続関係であり、メモリ領域の番号の大小ではありません。
`A` に記録された位置から `B` を見つけ、さらに `B` に記録された位置から `C` を見つけられるため、`A、B、C` の順にアクセスできます。
3. 論理的な順序は、メモリの区画番号の大小ではなく、ノード間に記録した接続関係で決まります。
`A` が記録する位置から `B` を、さらに `B` が記録する位置から `C` を見つけられるため、`A、B、C` の順にアクセスできます。
このことからも、論理構造と物理構造は、同じデータを異なる視点から捉えたものだと分かります。
このことからも、論理構造と物理構造は、同じデータを異なる二つの観点から捉えたものだと分かります。
### 宿題の記録におけるデータ型と構造
### 宿題の記録に使うデータ型と構造
ある学習グループが、座席順に 4 人の生徒が宿題を提出したかどうかを記録したところ、次の結果になりました。
ある学習グループで、4 人の生徒が宿題を提出したかどうかを座席順に記録したところ、次のようになりました。
`[true, false, true, true]`
<!-- numbered-subquestions -->
1. 各要素には、どの基本データ型が適していますか?
2. この 4 つの要素は座席順に一列に並んでいます。どのような論理構造を使っていますか?
3. 今後、各生徒の宿題の点数を `[90, 0, 85, 100]` と記録するように変えた場合、変わるのはデータの「内容の型」と「構成方法」のどちらですか?理由も説明してください。
1. 各要素には、どの基本データ型が適していますか。
2. 4 個の要素が座席順に一列に並ぶことで、どの論理構造を使っていますか。
3. 後で各自の宿題の点数 `[90, 0, 85, 100]` を記録するように変えた場合、変わるのはデータの「内容の型」と「組織化の方法」のどちらですか。理由を説明してください。
??? success "解答"
??? success "解答例"
1. 各要素は「はい」か「いいえ」だけを表すため、ブール型 `bool` が適しています。
1. 各要素は「はい」か「いいえ」だけを表すため、真偽値型 `bool` が適しています。
2. これらの要素は座席順に並んで線形構造を作っており、配列で保存できます。
2. 要素は座席順に並ぶ線形構造で、配列に保存できます。
3. 変わるのは内容の型です。要素がブール値から整数に変わります。一方、構成方法は変わりません。
3. 変わるのは内容の型です。要素が真偽値から整数になります。組織化の方法は変わらず、
データは引き続き座席順に一列に並び、配列という線形構造を使えます。
基本データ型は「何を保存するか」を、データ構造は「データをどのように構成するか」を表します。
基本データ型は「何を格納するか」を表し、データ構造は「データをどう組織化するか」を表します。
## プログラミング演習
### 二進表現に含まれる 1 の個数
### 二進表現に含まれる 1 の数
0 以上の整数 `n` が与えられます。その二進表現に 1 がいくつ含まれるかを数えてください。
非負整数 `n` が与えられます。その二進表現に含まれる 1 の総数を数えてください。
ビット演算を使って求めてください。二進表現を文字列に変換したり、1 を直接数える組み込み関数を使ったりしてはいけません。
ビット演算を使って解いてください。二進表現を文字列に変換したり、1 の数を直接数える組み込み関数を使ったりしてはいけません。
??? tip "解法のヒント"
??? tip "解くためのヒント"
1. n & 1 で n の最下位ビットを取り出し、そのビットが 1 かどうかを判定できます
2. 1 ビット右へシフトすると、現在の最下位ビットが取り除かれます。多くの言語では演算子 >> を使います
3. 各ビットを調べて右へシフトする方法を完成させたら、n & (n - 1) が n の最も右にある 1 を 0 に変えることも確認してみましょう
1. n & 1 で n の右端のビットを取り出し、そのビットが 1 かどうかを調べられます
2. 右に 1 ビットシフトすると、現在の右端のビットが捨てられます。多くの言語では演算子 >> を使います
3. 各ビットを調べて右シフトする方法を完成させてから、n & (n - 1) が n の右端の 1 を 0 に変えることを確かめてみましょう
[LeetCode](https://leetcode.com/problems/number-of-1-bits/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }

View File

@@ -4,6 +4,6 @@
!!! abstract
データ構造は、堅固で多様な枠組みのようなものである。
データ構造は、頑丈でありながら多彩な形を持つ骨組みのようです。
それはデータを秩序立てて組織するための青写真を示し、アルゴリズムはその上で生き生きと動き出す。
データを秩序立てて整理する設計図となり、その上でアルゴリズムが生き生きと働きます。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -1,24 +1,24 @@
# 数値エンコーディング *
# 数値の符号化 *
!!! tip
本書では、タイトルに * 記号が付いている章は選読です。時間が限られている場合や理解が難しいと感じる場合は、いったん読み飛ばし、必読章を終えてから個別に取り組んでください。
本書で見出しに * が付く節は、任意の学習内容です。時間が足りない場合や難しく感じる場合は、ひとまず飛ばし、必読の節を学んだ後で取り組んでかまいません。
## 符号付き絶対値表現、1 の補数、2 の補数
## 符号絶対値表現、1 の補数表現、2 の補数表現
前節の表を見ると、すべての整数型で表せる負数の個数は正数より 1 つ多く、たとえば `byte` の値域は $[-128, 127]$ です。この現象は直感に反するように見えますが、その背景には符号付き絶対値表現、1 の補数、2 の補数に関する知識があります。
前の節の表では、どの整数型も、表せる負の数が正の数より一つ多くなっていました。たとえば `byte` の範囲は $[-128, 127]$ です。直感に反するこの性質には、符号絶対値表現、1 の補数表現、2 の補数表現が関わります。
まず押さえておくべきなのは、**数値はコンピュータ内で「2 の補数」の形で保存される**ということです。その理由を説明する前に、まずはこの 3 つの定義を示します。
まず、**数値はコンピュータ内で「2 の補数表現」により保存される**ことを押さえましょう。理由を考える前に、三つを定義します。
- **符号付き絶対値表現**:数値の二進表現の最上位ビットを符号ビットとみなし、$0$ は正数、$1$ は負数を表し、残りのビットが数値の値を表します。
- **1 の補数**:正数の 1 の補数は符号付き絶対値表現と同じで、負数の 1 の補数は符号ビットを除くすべてのビットを反転したものです。
- **2 の補数**:正数の 2 の補数は符号付き絶対値表現と同じで、負数の 2 の補数は 1 の補数に $1$ を加えたものです。
- **符号絶対値表現**:二進表現の最上位ビットを符号とし、$0$ を正、$1$ を負とします。残りのビットで数の値を表します。
- **1 の補数表現**:正の数は符号絶対値表現と同じです。負の数は、符号絶対値表現の符号ビット以外をすべて反転します。
- **2 の補数表現**:正の数は符号絶対値表現と同じです。負の数は、1 の補数表現に $1$ を加えます。
下図は、符号付き絶対値表現、1 の補数、2 の補数の変換方法を示しています。
下図に、三つの表現の変換方法を示します。
![符号付き絶対値表現、1 の補数、2 の補数の相互変換](number_encoding.assets/1s_2s_complement.png)
![符号絶対値表現、1 の補数表現、2 の補数表現の相互変換](number_encoding.assets/1s_2s_complement.png)
<u>符号付き絶対値表現(sign-magnitude)</u>は最も直感的ですが、いくつかの制約があります。まず、**負数の符号付き絶対値表現はそのまま演算に使えません**。たとえば符号付き絶対値表現で $1 + (-2)$ を計算すると、結果は $-3$ になってしまい、これは明らかに誤りです。
<u>符号絶対値表現(sign-magnitude)</u>は最も直感的ですが、制約があります。一つは、**負の数をそのまま演算に使えないこと**です。たとえば $1 + (-2)$ をこの表現で計算すると、明らかに誤った結果 $-3$ になります。
$$
\begin{aligned}
@@ -29,20 +29,20 @@ $$
\end{aligned}
$$
この問題を解決するために、コンピュータには<u>1 の補数(1's complement)</u>が導入されました。まず符号付き絶対値表現を 1 の補数に変換し、1 の補数で $1 + (-2)$ を計算してから、結果を 1 の補数から符号付き絶対値表現へ戻すと、正しい結果 $-1$ が得られます。
この問題を解決するため、<u>1 の補数表現(1's complement)</u>が導入されました。符号絶対値表現から変換して $1 + (-2)$ を計算し、結果を元の表現へ戻すと、正しい答え $-1$ が得られます。
$$
\begin{aligned}
& 1 + (-2) \newline
& \rightarrow 0000 \; 0001 \; \text{(符号付き絶対値表現)} + 1000 \; 0010 \; \text{(符号付き絶対値表現)} \newline
& \rightarrow 0000 \; 0001 \; \text{(符号絶対値)} + 1000 \; 0010 \; \text{(符号絶対値)} \newline
& = 0000 \; 0001 \; \text{(1 の補数)} + 1111 \; 1101 \; \text{(1 の補数)} \newline
& = 1111 \; 1110 \; \text{(1 の補数)} \newline
& = 1000 \; 0001 \; \text{(符号付き絶対値表現)} \newline
& = 1000 \; 0001 \; \text{(符号絶対値)} \newline
& \rightarrow -1
\end{aligned}
$$
一方、**数値 0 の符号付き絶対値表現には $+0$ と $-0$ の 2 つの表し方があります**。つまり、数値 0 に対して異なる 2 つの二進コードが対応しており、これは曖昧さの原因になります。たとえば条件判定で正のゼロと負のゼロを区別しないと、誤った判定結果になる可能性があります。また、この曖昧さを解消しようとすると追加の判定処理が必要になり、計算効率が下がるおそれがあります。
もう一つは、**ゼロの符号絶対値表現が $+0$ と $-0$ の二つあること**です。ゼロに異なる二進符号が対応すると、曖昧さが生じる可能性があります。たとえば条件判定で正負のゼロを適切に区別しなければ、誤った結果になるかもしれません。この曖昧さに対応するには追加の判定が必要で、演算効率を下げる可能性があります。
$$
\begin{aligned}
@@ -51,26 +51,26 @@ $$
\end{aligned}
$$
符号付き絶対値表現と同様に、1 の補数にも正負のゼロの曖昧さがあります。そこでコンピュータはさらに<u>2 の補数(2's complement)</u>を導入しました。まずは負のゼロについて、符号付き絶対値表現、1 の補数、2 の補数の変換を見てみましょう。
1 の補数表現にも正負のゼロの問題があるため、さらに<u>2 の補数表現(2's complement)</u>が導入されました。まず、負のゼロの変換を見てみましょう。
$$
\begin{aligned}
-0 \rightarrow \; & 1000 \; 0000 \; \text{(符号付き絶対値表現)} \newline
-0 \rightarrow \; & 1000 \; 0000 \; \text{(符号絶対値)} \newline
= \; & 1111 \; 1111 \; \text{(1 の補数)} \newline
= 1 \; & 0000 \; 0000 \; \text{(2 の補数)} \newline
\end{aligned}
$$
負のゼロの 1 の補数に $1$ を加えると桁上がりが発生しますが、`byte` 型の長さは 8 ビットしかないため、第 9 ビットへあふれた $1$ は捨てられます。つまり、**負のゼロの 2 の補数は $0000 \; 0000$ であり、正のゼロの 2 の補数と同じです**。そのため、2 の補数表現ではゼロは 1 つしか存在せず、正負のゼロの曖昧さは解消されます。
負のゼロの 1 の補数に $1$ を加えると桁上がりしますが、`byte` は 8 ビットなので、9 ビット目にはみ出た $1$ は捨てられます。つまり、**負のゼロの 2 の補数は $0000 \; 0000$ で、正のゼロと同じです**。ゼロの表現が一つになり、曖昧さが解消されます。
最後にもう 1 つ疑問が残ります。`byte` 型の値域は $[-128, 127]$ ですが、余分にある負数 $-128$ はどのように得られるのでしょうか。区間 $[-127, +127]$ にあるすべての整数には、それぞれ対応する符号付き絶対値表現、1 の補数、2 の補数があり、符号付き絶対値表現と 2 の補数の間は相互に変換できます。
最後に、`byte` の範囲 $[-128, 127]$ で、一つ余分な負の数 $-128$ がどこから来るのかを考えます。$[-127, +127]$ の整数には、符号絶対値表現、1 の補数表現、2 の補数表現があり、符号絶対値表現と 2 の補数表現は相互変換できます。
しかし、**2 の補数 $1000 \; 0000$ だけは例外で、対応する符号付き絶対値表現を持ちません**。変換規則に従うと、この 2 の補数に対応する符号付き絶対値表現は $0000 \; 0000$ になります。これは明らかに矛盾しています。なぜなら、この符号付き絶対値表現は数値 $0$ を表し、その 2 の補数は自分自身であるはずだからです。コンピュータでは、この特別な 2 の補数 $1000 \; 0000$ を $-128$ と定めています。実際、2 の補数での $(-1) + (-127)$ の計算結果はちょうど $-128$ になります。
ただし、**2 の補数 $1000 \; 0000$ は例外で、対応する符号絶対値表現がありません**。変換手順に従うと $0000 \; 0000$ になりますが、これは数値 $0$ で、その 2 の補数表現は自身であるはずなので矛盾します。この特殊な $1000 \; 0000$ は、$-128$ を表すと定められています。実際、2 の補数で $(-1) + (-127)$ を計算すると $-128$ になります。
$$
\begin{aligned}
& (-127) + (-1) \newline
& \rightarrow 1111 \; 1111 \; \text{(符号付き絶対値表現)} + 1000 \; 0001 \; \text{(符号付き絶対値表現)} \newline
& \rightarrow 1111 \; 1111 \; \text{(符号絶対値)} + 1000 \; 0001 \; \text{(符号絶対値)} \newline
& = 1000 \; 0000 \; \text{(1 の補数)} + 1111 \; 1110 \; \text{(1 の補数)} \newline
& = 1000 \; 0001 \; \text{(2 の補数)} + 1111 \; 1111 \; \text{(2 の補数)} \newline
& = 1000 \; 0000 \; \text{(2 の補数)} \newline
@@ -78,43 +78,43 @@ $$
\end{aligned}
$$
すでにお気づきかもしれませんが、上の計算はすべて加算です。これは重要な事実を示しています。**コンピュータ内部のハードウェア回路は、主として加算を基準に設計されている**のです。なぜなら、加算はほかの演算(乗算、除算、減算など)に比べてハードウェアで実装しやすく、並列化もしやすく、演算速度も速いからです。
ここまでの計算がすべて加算であることに気づいたかもしれません。これは重要な事実、**コンピュータ内部の回路は主に加算を基礎に設計されていること**を示しています。加算は乗算・除算・減算などに比べて回路を実装しやすく、並列化も容易で、演算速度が速いためです。
ただし、これはコンピュータが加算しかできないという意味ではありません。**加算といくつかの基本的な論理演算を組み合わせることで、コンピュータはさまざまな数学演算を実現できます**。たとえば減算 $a - b$ は加算 $a + (-b)$ に変換できますし、乗算や除算も繰り返しの加算または減算に変換できます。
もちろん、加算しかできないという意味ではありません。**加算と基本的な論理演算を組み合わせれば、さまざまな数学的演算を実現できます**。たとえば減算 $a - b$ は加算 $a + (-b)$ に置き換えられ、乗算と除算も、複数回の加算や減算に変換できます。
これで、コンピュータが 2 の補数を使う理由をまとめられます。2 の補数表現に基づけば、コンピュータは同じ回路と操作で正数と負数の加算を扱うことができ、減算専用の特別なハードウェア回路を設計する必要がなく、正負のゼロの曖昧さも特別に処理しなくて済みます。これにより、ハードウェア設計は大幅に簡略化され、演算効率も向上します。
2 の補数を使う理由は、正負の加算を同じ回路と操作で処理でき、減算専用の回路や正負のゼロへの特別な処理が不要になるためです。これにより、回路設計が大幅に簡単になり、演算効率が高まります。
2 の補数の設計は非常に巧妙ですが、紙幅の都合上ここまでにします。興味のある読者は、さらに深く調べてみてください。
2 の補数の設計は巧妙ですが、紙幅の都合上、ここまでにします。興味があれば、さらに詳しく調べてみてください。
## 浮動小数点数のエンコーディング
## 浮動小数点数の符号化
注意深い人なら気づくかもしれません。`int` と `float` はどちらも長さが 4 バイトで同じなのに、なぜ `float` の値域は `int` よりはるかに広いのでしょうか。これはかなり直感に反します。というのも、`float` は小数を表す必要があるので、本来なら値域は狭くなるはずだからです。
`int` と `float` はどちらも 4 バイトなのに、なぜ `float` の方がはるかに広い範囲を表せるのでしょうか。小数も表すなら範囲が狭くなりそうなので、直感に反します。
実際には、**これは浮動小数点数 `float` が異なる表現方法を採用しているためです**。32 ビット長の二進数を次のように表します。
実際には、**`float` が別の表現方法を使っているためです**。32 ビットの二進数を次のように表します。
$$
b_{31} b_{30} b_{29} \ldots b_2 b_1 b_0
$$
IEEE 754 標準によれば、32-bit 長の `float` は次の 3 つの部分から構成されます。
IEEE 754 規格によれば、32-bit の `float` は三つの部分からなります。
- 符号部 $\mathrm{S}$ :1 ビットを占め、$b_{31}$ に対応します。
- 指数部 $\mathrm{E}$ :8 ビットを占め、$b_{30} b_{29} \ldots b_{23}$ に対応します。
- 仮数部 $\mathrm{N}$ :23 ビットを占め、$b_{22} b_{21} \ldots b_0$ に対応します。
- 符号部 $\mathrm{S}$:1 ビットで、$b_{31}$ に対応します。
- 指数部 $\mathrm{E}$:8 ビットで、$b_{30} b_{29} \ldots b_{23}$ に対応します。
- 仮数部 $\mathrm{N}$:23 ビットで、$b_{22} b_{21} \ldots b_0$ に対応します。
二進数 `float` に対応する値は次式で計算されます。
二進数で表した `float` の値は、次の式で計算します。
$$
\text {val} = (-1)^{b_{31}} \times 2^{\left(b_{30} b_{29} \ldots b_{23}\right)_2-127} \times\left(1 . b_{22} b_{21} \ldots b_0\right)_2
$$
十進数に直すと、計算式は次のようになります。
十進数の式に直すと、次のようになります。
$$
\text {val}=(-1)^{\mathrm{S}} \times 2^{\mathrm{E} -127} \times (1 + \mathrm{N})
$$
各項の取り得る範囲は次のとおりです。
各項の範囲は次のとおりです。
$$
\begin{aligned}
@@ -123,28 +123,28 @@ $$
\end{aligned}
$$
![IEEE 754 標準における float の計算例](number_encoding.assets/ieee_754_float.png)
![IEEE 754 に従った float の計算例](number_encoding.assets/ieee_754_float.png)
上図を見ると、例として $\mathrm{S} = 0$ 、 $\mathrm{E} = 124$ 、$\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$ が与えられた場合、次のようになります。
上図の例では、$\mathrm{S} = 0$、$\mathrm{E} = 124$、$\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$ なので、次の値になります。
$$
\text { val } = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875
$$
これで最初の疑問に答えられます。**`float` の表現方法には指数部が含まれているため、その値域は `int` よりはるかに広い**のです。上の計算より、`float` が表せる最大の正数は $2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}$ であり、符号ビットを切り替えれば最小の負数が得られます。
これで最初の疑問に答えられます。**`float` には指数部があるため、`int` よりはるかに広い範囲を表せるのです**。上の計算から、最大の正の数は $2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}$ となり、符号を変えると最小の負の数になります。
**浮動小数点数 `float` は値域を広げる一方で、その代償として精度を犠牲にしています**。整数型 `int` は 32 ビットすべてを数値の表現に使うため、数値は一様に分布します。しかし指数部があるため、浮動小数点数 `float` は値が大きくなるほど、隣り合う 2 つの数の差も大きくなる傾向があります。
**`float` は範囲を広げる代わりに、精度を犠牲にしています**。`int` は 32 ビット全体で数を表し、値は等間隔に分布します。一方、`float` は指数部があるため、数値が大きいほど、隣り合う表現可能な数の間隔も大きくなる傾向があります。
次の表のとおり、指数部 $\mathrm{E} = 0$ と $\mathrm{E} = 255$ には特別な意味があり、**ゼロ、無限大、$\mathrm{NaN}$ などを表すために使われます**。
下表のように、指数部 $\mathrm{E} = 0$ と $\mathrm{E} = 255$ は特殊な意味を持ち、**ゼロ、無限大、$\mathrm{NaN}$ などを表すために使います**。
<p align="center"> 表 <id> &nbsp; 指数部の意味 </p>
| 指数部 E | 仮数部 $\mathrm{N} = 0$ | 仮数部 $\mathrm{N} \ne 0$ | 計算式 |
| 指数部 E | 仮数部 $\mathrm{N} = 0$ | 仮数部 $\mathrm{N} \ne 0$ | 計算式 |
| ------------------ | ----------------------- | ------------------------- | ---------------------------------------------------------------------- |
| $0$ | $\pm 0$ | 非正規化数 | $(-1)^{\mathrm{S}} \times 2^{-126} \times (0.\mathrm{N})$ |
| $1, 2, \dots, 254$ | 正規化数 | 正規化数 | $(-1)^{\mathrm{S}} \times 2^{(\mathrm{E} -127)} \times (1.\mathrm{N})$ |
| $0$ | $\pm 0$ | 非正規化数 | $(-1)^{\mathrm{S}} \times 2^{-126} \times (0.\mathrm{N})$ |
| $1, 2, \dots, 254$ | 正規化数 | 正規化数 | $(-1)^{\mathrm{S}} \times 2^{(\mathrm{E} -127)} \times (1.\mathrm{N})$ |
| $255$ | $\pm \infty$ | $\mathrm{NaN}$ | |
なお、非正規化数によって浮動小数点数の精度は大きく向上します。最小の正の正規化数は $2^{-126}$ であり、最小の正の非正規化数は $2^{-126} \times 2^{-23}$ です。
非正規化数は、浮動小数点数の精度を大きく改善します。最小の正の正規化数は $2^{-126}$、最小の正の非正規化数は $2^{-126} \times 2^{-23}$ です。
倍精度 `double` も `float` と同様の表現方法を採用しているため、ここでは詳述しません。
倍精度の `double` も `float` に似た方法で表現するため、ここでは説明を省きます。

View File

@@ -1,66 +1,66 @@
# まとめ
### 重要ポイントの振り返り
### 要点の振り返り
- データ構造は、論理構造と物理構造という 2 つの観点から分類できます。論理構造はデータ要素間の論理的関係を記述し、物理構造はデータのコンピュータメモリ上での格納方法を記述します。
- 代表的な論理構造には、線形、木構造、網状構造などがあります。通常、論理構造に基づいてデータ構造を線形(配列、連結リスト、スタック、キュー)と非線形(木、グラフ、ヒープ)の 2 種類に分類します。ハッシュテーブルの実装には、線形データ構造と非線形データ構造が同時に含まれる場合があります。
- プログラムの実行時、データはコンピュータメモリに格納されます。各メモリ空間には対応するメモリアドレスがあり、プログラムはそれらのメモリアドレスを通じてデータにアクセスします。
- 物理構造は主に連続領域への格納(配列)と分散領域への格納(連結リスト)に分けられます。すべてのデータ構造は、配列、連結リスト、またはその両方の組み合わせによって実装されます。
- コンピュータにおける基本データ型には、整数 `byte`、`short`、`int`、`long`、浮動小数点数 `float`、`double`、文字 `char`、真偽値 `bool` があります。これらの値域は、使用する記憶領域の大きさと表現方式によって決まります。
- 符号付き絶対値表現、1 の補数、2 の補数は、コンピュータで数値を符号化する 3 つの方法であり、相互に変換できます。整数の符号付き絶対値表現では最上位ビットが符号ビットで、残りのビットが数値の値です。
- 整数はコンピュータ内では 2 の補数の形式で格納されます。2 の補数表現では、コンピュータは正数と負数の加算を同じように扱うことができ、減算のために特別なハードウェア回路を別途設計する必要がなく、さらに正負のゼロが重複する問題もありません。
- 浮動小数点数の符号化は、1 ビットの符号部、8 ビットの指数部、23 ビットの仮数部で構成されます。指数部があるため、浮動小数点数の値域は整数よりはるかに広くなりますが、その代償として精度が犠牲になります。
- ASCII コードは最も早く登場した英字文字集合で、長さは 1 バイト、収録文字数は 128 です。GBK 文字集合はよく使われる中国語文字集合で、2 万字以上の漢字を収録しています。Unicode は完全な文字集合標準を提供することを目指しており、世界中のさまざまな言語の文字を収録することで、文字コード方式の不一致によって生じる文字化けの問題を解決します。
- UTF-8 は最も広く使われている Unicode の符号化方式で、汎用性が非常に高いです。可変長の符号化方式であり、拡張性に優れ、記憶領域の利用効率を効果的に高めます。UTF-16 と UTF-32 は固定長の符号化方式です。中国語を符号化する場合、UTF-16 は UTF-8 よりも使用領域が小さくなります。Java や C# などのプログラミング言語は、デフォルトで UTF-16 を使用します。
- データ構造は論理構造と物理構造の二つの観点で分類できます。論理構造は要素間の関係、物理構造はメモリ内での格納方法を表します。
- 主な論理構造には、線形、木、ネットワークがあります。通常は線形(配列、連結リスト、スタック、キュー)と非線形(木、グラフ、ヒープ)に分けます。ハッシュテーブルの実装には、線形と非線形の両方が含まれる場合があります。
- プログラムの実行時、データはメモリに保存されます。各領域にはメモリアドレスがあり、プログラムはそれを使ってデータにアクセスします。
- 物理構造は主に、連続した領域への格納(配列)と分散した領域への格納(連結リスト)に分けられます。すべてのデータ構造は、この二つまたはその組み合わせで実装されます。
- 基本データ型には、整数の `byte`、`short`、`int`、`long`、浮動小数点の `float`、`double`、文字の `char`、ブールの `bool` があります。値の範囲は、使用する空間と表現方法で決まります。
- 符号絶対値表現、1 の補数表現、2 の補数表現は、数値を符号化する三つの方法で、相互に変換できます。整数の符号絶対値表現では、最上位ビットが符号、残りが数の値を表します。
- 整数はコンピュータ内で 2 の補数表現で保存されます。正負の加算を同じ方法で処理でき、減算専用のハードウェア回路が不要になり、正負のゼロという曖昧さもなくなります。
- 浮動小数点数の符号化は、符号 1 ビット、指数 8 ビット、仮数 23 ビットで構成されます。指数部があるため整数よりはるかに広い範囲を表せますが、精度を犠牲にします。
- ASCII は初期の英語用文字集合で、長さは 1 バイト、128 文字を収録しています。GBK はよく使われる中国語の文字集合で、2 万字以上の漢字を収録しています。Unicode は、世界の各言語の文字を収めた完全な文字集合の標準を目指し、符号化方式の違いによる文字化けの解消を図っています。
- UTF-8 は最も普及した Unicode の符号化方式で、汎用性に優れています。可変長で拡張性が高く、記憶領域を効率よく使えます。UTF-16 と UTF-32 は固定長の符号化方式です。中国語では UTF-16 の方が UTF-8 より少ない空間で済みます。Java や C# などは標準で UTF-16 を使います。
### Q & A
**Q**:なぜハッシュテーブルには線形データ構造と非線形データ構造が同時に含まれるのですか?
**Q**:ハッシュテーブルに、線形と非線形のデータ構造の両方が含まれるのはなぜですか。
ハッシュテーブルの基盤は配列であり、ハッシュ衝突を解決するために「チェイン法」(後続の「ハッシュ衝突」の章で説明します)を使うことがあります。配列内の各バケットは 1 つの連結リストを指し、その連結リストの長さがある閾値を超えると、木(通常は赤黒木)に変換されることもあります。
ハッシュテーブルの基盤は配列ですが、衝突の解決には「チェイン法」(後の「ハッシュ衝突」の節で説明します)を使うことがあります。各バケットが連結リストを指し、その長さがしきい値を超えると、木(通常は赤黒木)に変換する場合があります。
格納の観点から見ると、ハッシュテーブルの基盤は配列であり、各バケットスロットには値が入ることもあれば、連結リストや木が入ることもあります。したがって、ハッシュテーブルには線形データ構造(配列、連結リスト)と非線形データ構造(木)が同時に含まれる場合があります。
格納の観点では、基盤となる配列の各バケットには、一つの値、連結リスト、木のいずれかが入る可能性があります。そのため、線形の構造(配列、連結リスト)と非線形の構造(木)が同時に含まれる場合があります。
**Q**:`char` 型の長さは 1 バイトですか?
**Q**:`char` 型の長さは 1 バイトですか。
`char` 型の長さは、プログラミング言語が採用する符号化方式によって決まります。たとえば、Java、JavaScript、TypeScript、C# はいずれも UTF-16 符号化(Unicode コードポイントを保持)を採用しているため、`char` 型の長さは 2 バイトです。
`char` の長さは、言語が使う符号化方式で決まります。たとえば Java、JavaScript、TypeScript、C# は UTF-16(Unicode コードポイントを保存する方式)を使うため、`char` の長さは 2 バイトです。
**Q**:配列ベースで実装されたデータ構造を「静的データ構造」と呼ぶのは曖昧ではありませんか? スタックも push や pop などの操作ができ、これらの操作はどれも「動的」です。
**Q**:配列で実装したデータ構造を「静的」と呼ぶのは曖昧ではありませんか。スタックのプッシュやポップは「動的」な操作です。
スタックは確かに動的なデータ操作を実現できますが、データ構造自体は依然として「静的」(長さが不変)です。配列ベースのデータ構造でも要素を動的に追加または削除できますが、その容量は固定です。データ量が事前に確保した大きさを超えた場合は、より大きな新しい配列を作成し、古い配列の内容を新しい配列にコピーする必要があります。
スタックのデータ操作は確かに動的ですが、構造自体は「静的」(長さが固定)です。配列を使った構造で要素を動的に追加・削除できても、容量は固定されています。確保済みの容量を超えたら、より大きな配列を作り、元の内容をコピーする必要があります。
**Q**:スタック(キュー)を構築するときにサイズを指定していないのに、なぜそれらは「静的データ構造」なのですか?
**Q**:スタックやキューを作るときに大きさを指定していないのに、なぜ「静的データ構造」なのですか。
高水準プログラミング言語では、スタック(キュー)の初期容量を人手で指定する必要はなく、この作業はクラス内部で自動的に行われます。たとえば、Java の `ArrayList` の初期容量は通常 10 です。また、容量拡張も自動的に実装されています。詳しくは後続の「リスト」の章を参照してください。
高水準言語では、初期容量の設定をクラスの内部で自動的に行うため、自分で指定する必要はありません。たとえば Java の `ArrayList` の初期容量は通常 10 です。容量拡張も自動で行われます。詳しくは後の「リスト」の節で説明します。
**Q**:符号付き絶対値表現から 2 の補数への変換方法は「先にビット反転してから 1 を加える」ですが、2 の補数から符号付き絶対値表現への変換は逆演算である「先に 1 を引いてからビット反転する」べきなのに、同じく「先にビット反転してから 1 を加える」でも求められます。これはなぜですか?
**Q**:符号絶対値表現から 2 の補数表現への変換は「反転してから 1 を足す」ので、逆変換は「1 を引いてから反転する」はずです。逆変換にも「反転してから 1 を足す」が使えるのはなぜですか。
これは、符号付き絶対値表現と 2 の補数の相互変換が、実際には「補数」を計算する過程だからです。まず補数の定義を示します。$a + b = c$ とすると、$a$ を $b$ から $c$ への補数と呼び、逆に $b$ も $a$ から $c$ への補数と呼びます。
相互変換は、実際には「補数」を求める操作だからです。まず補数を定義します。$a + b = c$ なら、$a$ は $b$ の $c$ に対する補数であり、逆に $b$ も $a$ の $c$ に対する補数です。
長さ $n = 4$ ビットの 2 進数 $0010$ が与えられたとします。この数を符号付き絶対値表現(符号ビットは考慮しない)とみなすと、その 2 の補数は「先にビット反転してから 1 を加える」ことで得られます。
$n = 4$ ビットの二進数 $0010$ を、符号ビットを考慮せず符号絶対値表現とみなすと、対応する 2 の補数は「反転してから 1 を足す」ことで得られます。
$$
0010 \rightarrow 1101 \rightarrow 1110
$$
ここで、符号付き絶対値表現と 2 の補数の和は $0010 + 1110 = 10000$ となります。つまり、2 の補数 $1110$ は符号付き絶対値表現 $0010$ から $10000$ への「補数」です。**これは、上記の「先にビット反転してから 1 を加える」が、実際には $10000$ への補数を計算する過程であることを意味します。**
元の値と補数の和は $0010 + 1110 = 10000$ です。つまり、補数 $1110$ は、元の値 $0010$ の $10000$ に対する「補数」です。**「反転してから 1 を足す」は、実際には $10000$ に対する補数を求める操作なのです**。
では、2 の補数 $1110$ から $10000$ への「補数」はいくつでしょうか。これもやはり「先にビット反転してから 1 を加える」ことで求められます。
では、$1110$ の $10000$ に対する「補数」は何でしょうか。同じく「反転してから 1 を足す」で求められます。
$$
1110 \rightarrow 0001 \rightarrow 0010
$$
言い換えると、符号付き絶対値表現と 2 の補数は互いに相手から $10000$ への「補数」なので、「符号付き絶対値表現から 2 の補数への変換」と「2 の補数から符号付き絶対値表現への変換」は同じ操作(先にビット反転してから 1 を加える)で実現できます。
つまり、元の表現と 2 の補数表現は、互いに $10000$ に対する「補数」なので、どちらへの変換も同じ操作(反転してから 1 を足す)で行えます。
もちろん、逆演算を用いて 2 の補数 $1110$ の符号付き絶対値表現を求めることもでき、その場合は「先に 1 を引いてからビット反転する」ことになります。
もちろん、$1110$ から元の表現を求めるには、「1 を引いてから反転する」という逆操作も使えます。
$$
1110 \rightarrow 1101 \rightarrow 0010
$$
まとめると、「先にビット反転してから 1 を加える」と「先に 1 を引いてからビット反転する」の 2 つの演算は、どちらも $10000$ への補数を計算しており、等価です。
「反転してから 1 を足す」と「1 を引いてから反転する」は、どちらも $10000$ に対する補数を求める操作であり、同等なのです。
本質的には、「ビット反転」という操作は実際には $1111$ への補数を求めています(常に `符号付き絶対値表現 + 1 の補数 = 1111` が成り立つため)。そして、1 の補数にさらに 1 を加えて得られる 2 の補数が、$10000$ への補数です。
本質的には、「反転」は $1111$ に対する補数を求める操作です(常に `元の値 + 1 の補数 = 1111` が成り立ちます)。さらに 1 を足して得る 2 の補数は、$10000$ に対する補数になります。
上記では $n = 4$ を例にしましたが、この考え方は任意のビット長の 2 進数に一般化できます。
ここでは $n = 4$ を例にしましたが、任意のビット数の二進数に一般化できます。