mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-07 21:24:18 +00:00
Re-translate the Japanese version (#1871)
* Retranslate Japanese docs with GPT-5.4 * Retranslate Japanese code with GPT-5.4
This commit is contained in:
@@ -2,23 +2,23 @@
|
||||
|
||||
!!! tip
|
||||
|
||||
本書では、アスタリスク「*」が付いた章は任意読書です。時間が不足している場合や難しいと感じる場合は、最初はこれらをスキップして、必須の章を完了した後に戻ることができます。
|
||||
本書では、タイトルに * 記号が付いている章は選読です。時間が限られている場合や理解が難しいと感じる場合は、いったん読み飛ばし、必読章を終えてから個別に取り組んでください。
|
||||
|
||||
## 整数エンコーディング
|
||||
## 符号付き絶対値表現、1 の補数、2 の補数
|
||||
|
||||
前の節の表で、すべての整数型は正の数よりも1つ多い負の数を表現できることを観察しました。例えば、`byte`の範囲は$[-128, 127]$です。この現象は直感に反するように見え、その根本的な理由には符号絶対値、1の補数、2の補数エンコーディングの知識が関与しています。
|
||||
前節の表を見ると、すべての整数型で表せる負数の個数は正数より 1 つ多く、たとえば `byte` の値域は $[-128, 127]$ です。この現象は直感に反するように見えますが、その背景には符号付き絶対値表現、1 の補数、2 の補数に関する知識があります。
|
||||
|
||||
まず重要なことは、**数値はコンピュータ内で2の補数形式で格納される**ということです。なぜそうなのかを分析する前に、これら3つのエンコーディング方法を定義しましょう:
|
||||
まず押さえておくべきなのは、**数値はコンピュータ内で「2 の補数」の形で保存される**ということです。その理由を説明する前に、まずはこの 3 つの定義を示します。
|
||||
|
||||
- **符号絶対値**:数値の二進表現の最上位ビットを符号ビットとし、$0$は正の数、$1$は負の数を表します。残りのビットは数値の値を表します。
|
||||
- **1の補数**:正の数の1の補数は符号絶対値と同じです。負の数の場合、符号ビット以外のすべてのビットを反転して得られます。
|
||||
- **2の補数**:正の数の2の補数は符号絶対値と同じです。負の数の場合、その1の補数に$1$を加えて得られます。
|
||||
- **符号付き絶対値表現**:数値の二進表現の最上位ビットを符号ビットとみなし、$0$ は正数、$1$ は負数を表し、残りのビットが数値の値を表します。
|
||||
- **1 の補数**:正数の 1 の補数は符号付き絶対値表現と同じで、負数の 1 の補数は符号ビットを除くすべてのビットを反転したものです。
|
||||
- **2 の補数**:正数の 2 の補数は符号付き絶対値表現と同じで、負数の 2 の補数は 1 の補数に $1$ を加えたものです。
|
||||
|
||||
以下の図は、符号絶対値、1の補数、2の補数間の変換を示しています:
|
||||
下図は、符号付き絶対値表現、1 の補数、2 の補数の変換方法を示しています。
|
||||
|
||||

|
||||

|
||||
|
||||
<u>符号絶対値</u>は最も直感的ですが、制限があります。一つには、**符号絶対値の負の数は計算で直接使用できません**。例えば、符号絶対値で$1 + (-2)$を計算すると$-3$になり、これは正しくありません。
|
||||
<u>符号付き絶対値表現(sign-magnitude)</u>は最も直感的ですが、いくつかの制約があります。まず、**負数の符号付き絶対値表現はそのまま演算に使えません**。たとえば符号付き絶対値表現で $1 + (-2)$ を計算すると、結果は $-3$ になってしまい、これは明らかに誤りです。
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
@@ -29,20 +29,20 @@ $$
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
この問題に対処するため、コンピュータは<u>1の補数</u>を導入しました。1の補数に変換して$1 + (-2)$を計算し、結果を符号絶対値に戻すと、正しい結果$-1$が得られます。
|
||||
この問題を解決するために、コンピュータには<u>1 の補数(1's complement)</u>が導入されました。まず符号付き絶対値表現を 1 の補数に変換し、1 の補数で $1 + (-2)$ を計算してから、結果を 1 の補数から符号付き絶対値表現へ戻すと、正しい結果 $-1$ が得られます。
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
& 1 + (-2) \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
|
||||
& \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
|
||||
& \rightarrow -1
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
また、**符号絶対値では0に2つの表現があります**:$+0$と$-0$です。これは0に対して2つの異なる二進エンコーディングがあることを意味し、曖昧さを引き起こす可能性があります。例えば、条件チェックで正と負の0を区別しないと、正しくない結果になる可能性があります。この曖昧さに対処するには追加のチェックが必要で、計算効率が低下する可能性があります。
|
||||
一方、**数値 0 の符号付き絶対値表現には $+0$ と $-0$ の 2 つの表し方があります**。つまり、数値 0 に対して異なる 2 つの二進コードが対応しており、これは曖昧さの原因になります。たとえば条件判定で正のゼロと負のゼロを区別しないと、誤った判定結果になる可能性があります。また、この曖昧さを解消しようとすると追加の判定処理が必要になり、計算効率が下がるおそれがあります。
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
@@ -51,100 +51,100 @@ $$
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
符号絶対値と同様に、1の補数も正と負の0の曖昧さに悩まされます。そのため、コンピュータはさらに<u>2の補数</u>を導入しました。符号絶対値、1の補数、2の補数における負の0の変換過程を観察してみましょう:
|
||||
符号付き絶対値表現と同様に、1 の補数にも正負のゼロの曖昧さがあります。そこでコンピュータはさらに<u>2 の補数(2's complement)</u>を導入しました。まずは負のゼロについて、符号付き絶対値表現、1 の補数、2 の補数の変換を見てみましょう。
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
-0 \rightarrow \; & 1000 \; 0000 \; \text{(符号絶対値)} \newline
|
||||
= \; & 1111 \; 1111 \; \text{(1の補数)} \newline
|
||||
= 1 \; & 0000 \; 0000 \; \text{(2の補数)} \newline
|
||||
-0 \rightarrow \; & 1000 \; 0000 \; \text{(符号付き絶対値表現)} \newline
|
||||
= \; & 1111 \; 1111 \; \text{(1 の補数)} \newline
|
||||
= 1 \; & 0000 \; 0000 \; \text{(2 の補数)} \newline
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
負の0の1の補数に$1$を加えると桁上がりが発生しますが、`byte`の長さは8ビットのみのため、9番目のビットへの桁上がり$1$は破棄されます。したがって、**負の0の2の補数は$0000 \; 0000$**で、正の0と同じになり、曖昧さが解決されます。
|
||||
負のゼロの 1 の補数に $1$ を加えると桁上がりが発生しますが、`byte` 型の長さは 8 ビットしかないため、第 9 ビットへあふれた $1$ は捨てられます。つまり、**負のゼロの 2 の補数は $0000 \; 0000$ であり、正のゼロの 2 の補数と同じです**。そのため、2 の補数表現ではゼロは 1 つしか存在せず、正負のゼロの曖昧さは解消されます。
|
||||
|
||||
最後の謎は、`byte`の$[-128, 127]$の範囲で、追加の負の数$-128$があることです。$[-127, +127]$の区間では、すべての整数に対応する符号絶対値、1の補数、2の補数があり、相互変換が可能であることを観察します。
|
||||
最後にもう 1 つ疑問が残ります。`byte` 型の値域は $[-128, 127]$ ですが、余分にある負数 $-128$ はどのように得られるのでしょうか。区間 $[-127, +127]$ にあるすべての整数には、それぞれ対応する符号付き絶対値表現、1 の補数、2 の補数があり、符号付き絶対値表現と 2 の補数の間は相互に変換できます。
|
||||
|
||||
しかし、**2の補数$1000 \; 0000$は対応する符号絶対値を持たない例外です**。変換方法によると、その符号絶対値は$0000 \; 0000$で、0を示します。これは矛盾を示しています。なぜなら、その2の補数は自分自身を表すべきだからです。コンピュータは、この特別な2の補数$1000 \; 0000$を$-128$を表すものとして指定しています。実際、2の補数での$(-1) + (-127)$の計算結果は$-128$になります。
|
||||
しかし、**2 の補数 $1000 \; 0000$ だけは例外で、対応する符号付き絶対値表現を持ちません**。変換規則に従うと、この 2 の補数に対応する符号付き絶対値表現は $0000 \; 0000$ になります。これは明らかに矛盾しています。なぜなら、この符号付き絶対値表現は数値 $0$ を表し、その 2 の補数は自分自身であるはずだからです。コンピュータでは、この特別な 2 の補数 $1000 \; 0000$ を $-128$ と定めています。実際、2 の補数での $(-1) + (-127)$ の計算結果はちょうど $-128$ になります。
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
& (-127) + (-1) \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
|
||||
& \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
|
||||
& \rightarrow -128
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
お気づきかもしれませんが、これらの計算はすべて加算であり、重要な事実を示唆しています:**コンピュータの内部ハードウェア回路は主に加算演算を中心に設計されています**。これは、加算が乗算、除算、減算などの他の演算と比較してハードウェアで実装しやすく、並列化が容易で高速計算が可能だからです。
|
||||
すでにお気づきかもしれませんが、上の計算はすべて加算です。これは重要な事実を示しています。**コンピュータ内部のハードウェア回路は、主として加算を基準に設計されている**のです。なぜなら、加算はほかの演算(乗算、除算、減算など)に比べてハードウェアで実装しやすく、並列化もしやすく、演算速度も速いからです。
|
||||
|
||||
これはコンピュータが加算のみを実行できることを意味するものではありません。**加算と基本的な論理演算を組み合わせることで、コンピュータは様々な他の数学演算を実行できます**。例えば、減算$a - b$は$a + (-b)$に変換でき、乗算と除算は複数の加算または減算に変換できます。
|
||||
ただし、これはコンピュータが加算しかできないという意味ではありません。**加算といくつかの基本的な論理演算を組み合わせることで、コンピュータはさまざまな数学演算を実現できます**。たとえば減算 $a - b$ は加算 $a + (-b)$ に変換できますし、乗算や除算も繰り返しの加算または減算に変換できます。
|
||||
|
||||
コンピュータで2の補数を使用する理由をまとめることができます:2の補数表現により、コンピュータは同じ回路と演算を使用して正と負の数の加算を処理でき、減算用の特別なハードウェア回路の必要性を排除し、正と負の0の曖昧さを回避できます。これによりハードウェア設計が大幅に簡素化され、計算効率が向上します。
|
||||
これで、コンピュータが 2 の補数を使う理由をまとめられます。2 の補数表現に基づけば、コンピュータは同じ回路と操作で正数と負数の加算を扱うことができ、減算専用の特別なハードウェア回路を設計する必要がなく、正負のゼロの曖昧さも特別に処理しなくて済みます。これにより、ハードウェア設計は大幅に簡略化され、演算効率も向上します。
|
||||
|
||||
2の補数の設計は非常に巧妙で、スペースの制約により、ここで停止します。興味のある読者はさらに探求することを奨励します。
|
||||
2 の補数の設計は非常に巧妙ですが、紙幅の都合上ここまでにします。興味のある読者は、さらに深く調べてみてください。
|
||||
|
||||
## 浮動小数点数エンコーディング
|
||||
## 浮動小数点数のエンコーディング
|
||||
|
||||
興味深いことに気づいたかもしれません:同じ4バイトの長さにもかかわらず、なぜ`float`は`int`と比較してはるかに大きい値の範囲を持つのでしょうか?これは直感に反するように見えます。`float`は分数を表現する必要があるため、範囲が縮小すると予想されるからです。
|
||||
注意深い人なら気づくかもしれません。`int` と `float` はどちらも長さが 4 バイトで同じなのに、なぜ `float` の値域は `int` よりはるかに広いのでしょうか。これはかなり直感に反します。というのも、`float` は小数を表す必要があるので、本来なら値域は狭くなるはずだからです。
|
||||
|
||||
実際、**これは浮動小数点数(`float`)で使用される異なる表現方法によるものです**。32ビットの二進数を次のように考えてみましょう:
|
||||
実際には、**これは浮動小数点数 `float` が異なる表現方法を採用しているためです**。32 ビット長の二進数を次のように表します。
|
||||
|
||||
$$
|
||||
b_{31} b_{30} b_{29} \ldots b_2 b_1 b_0
|
||||
$$
|
||||
|
||||
IEEE 754標準によると、32ビットの`float`は次の3つの部分で構成されます:
|
||||
IEEE 754 標準によれば、32-bit 長の `float` は次の 3 つの部分から構成されます。
|
||||
|
||||
- 符号ビット$\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)^{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})
|
||||
\text {val}=(-1)^{\mathrm{S}} \times 2^{\mathrm{E} -127} \times (1 + \mathrm{N})
|
||||
$$
|
||||
|
||||
各成分の範囲は:
|
||||
各項の取り得る範囲は次のとおりです。
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
\mathrm{S} \in & \{ 0, 1\}, \quad \mathrm{E} \in \{ 1, 2, \dots, 254 \} \newline
|
||||
(1 + \mathrm{N}) = & (1 + \sum_{i=1}^{23} b_{23-i} \times 2^{-i}) \subset [1, 2 - 2^{-23}]
|
||||
(1 + \mathrm{N}) = & (1 + \sum_{i=1}^{23} b_{23-i} 2^{-i}) \subset [1, 2 - 2^{-23}]
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||

|
||||

|
||||
|
||||
上の図を観察すると、例のデータ$\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
|
||||
\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` よりはるかに広い**のです。上の計算より、`float` が表せる最大の正数は $2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}$ であり、符号ビットを切り替えれば最小の負数が得られます。
|
||||
|
||||
**しかし、`float`の拡張された範囲のトレードオフは精度の犠牲です**。整数型`int`は32ビットすべてを数値表現に使用し、値は均等に分布していますが、指数ビットのため、`float`の値が大きいほど、隣接する数値間の差が大きくなります。
|
||||
**浮動小数点数 `float` は値域を広げる一方で、その代償として精度を犠牲にしています**。整数型 `int` は 32 ビットすべてを数値の表現に使うため、数値は一様に分布します。しかし指数部があるため、浮動小数点数 `float` は値が大きくなるほど、隣り合う 2 つの数の差も大きくなる傾向があります。
|
||||
|
||||
以下の表に示すように、指数ビット$\mathrm{E} = 0$と$\mathrm{E} = 255$は特別な意味を持ち、**0、無限大、$\mathrm{NaN}$などを表現するために使用されます**。
|
||||
次の表のとおり、指数部 $\mathrm{E} = 0$ と $\mathrm{E} = 255$ には特別な意味があり、**ゼロ、無限大、$\mathrm{NaN}$ などを表すために使われます**。
|
||||
|
||||
<p align="center"> 表 <id> 指数ビットの意味 </p>
|
||||
<p align="center"> 表 <id> 指数部の意味 </p>
|
||||
|
||||
| 指数ビット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})$ |
|
||||
| $255$ | $\pm \infty$ | $\mathrm{NaN}$ | |
|
||||
| 指数部 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})$ |
|
||||
| $255$ | $\pm \infty$ | $\mathrm{NaN}$ | |
|
||||
|
||||
非正規化数は浮動小数点数の精度を大幅に向上させることは注目に値します。最小の正の正規化数は$2^{-126}$で、最小の正の非正規化数は$2^{-126} \times 2^{-23}$です。
|
||||
なお、非正規化数によって浮動小数点数の精度は大きく向上します。最小の正の正規化数は $2^{-126}$ であり、最小の正の非正規化数は $2^{-126} \times 2^{-23}$ です。
|
||||
|
||||
倍精度`double`も`float`と同様の表現方法を使用しますが、簡潔さのためここでは詳述しません。
|
||||
倍精度 `double` も `float` と同様の表現方法を採用しているため、ここでは詳述しません。
|
||||
|
||||
Reference in New Issue
Block a user