Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 14223] ファイルの概要

このコミットは、Go言語の標準ライブラリ encoding/binary パッケージ内の varint.go ファイルに対する変更です。encoding/binary パッケージは、Goのデータ構造とバイトシーケンス間の変換、特に可変長整数(varint)のエンコードとデコードを提供します。varint.go は、このパッケージ内で可変長整数の読み書きに関する具体的な実装を含んでいます。

コミット

encoding/binary: ReadVarint reads a signed number, not unsigned number

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/f9902c7197f436578e8fafa7946d8cd83467729e

元コミット内容

commit f9902c7197f436578e8fafa7946d8cd83467729e
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Fri Oct 26 21:14:34 2012 +0800

    encoding/binary: ReadVarint reads a signed number, not unsigned number
    
    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/6762051

変更の背景

このコミットの背景は、encoding/binary パッケージの ReadVarint 関数のコメントが、その実際の動作と一致していなかったことです。ReadVarint 関数は、内部的に符号なし整数を読み取る ReadUvarint を利用しつつ、結果として符号付き整数を正しくデコードするロジック(ジグザグエンコーディング)を含んでいました。しかし、既存のコメントは「符号なし整数を読み取る」と誤って記述されており、これが関数の意図や動作を誤解させる可能性がありました。

この変更は、コードの動作自体を変更するものではなく、ドキュメント(コメント)を修正することで、関数の真の目的と動作を正確に反映させ、開発者の混乱を防ぐことを目的としています。これは、コードの可読性と保守性を向上させるための重要なステップです。

前提知識の解説

1. 可変長整数 (Varint)

可変長整数(Varint)は、数値を効率的にエンコードするための手法です。特に、小さい数値は少ないバイト数で表現し、大きい数値はより多くのバイト数で表現することで、全体的なデータサイズを削減します。GoogleのProtocol Buffersなどで広く使用されています。

Varintの基本的な考え方は、各バイトの最上位ビット(MSB)を「継続ビット」として使用することです。MSBが1の場合、その数値は次のバイトにも続くことを示し、MSBが0の場合、そのバイトが数値の最後のバイトであることを示します。残りの7ビットが数値のデータとして使用されます。

2. 符号付き整数と符号なし整数

  • 符号なし整数 (Unsigned Integer): 0以上の整数のみを表現します。すべてのビットが数値の大きさを表すために使用されます。
  • 符号付き整数 (Signed Integer): 正の数、負の数、および0を表現します。通常、最上位ビットが符号ビットとして使用され、0は正、1は負を示します(2の補数表現が一般的)。

3. ジグザグエンコーディング (ZigZag Encoding)

Varintは通常、符号なし整数をエンコードするために設計されています。しかし、負の数をVarintで効率的に表現するためには、ジグザグエンコーディングが使用されます。

ジグザグエンコーディングは、符号付き整数を符号なし整数にマッピングする手法です。これにより、絶対値が小さい負の数(例: -1, -2)も、絶対値が小さい正の数(例: 1, 2)と同様に、Varintで少ないバイト数で表現できるようになります。

マッピングのルールは以下の通りです(例: 32ビット整数):

  • n が正の場合: (n << 1)
  • n が負の場合: (~n << 1) | 1

これにより、以下のようなマッピングが行われます。

  • 0 -> 0
  • -1 -> 1
  • 1 -> 2
  • -2 -> 3
  • 2 -> 4
  • ...

デコード時には、この逆の操作を行います。

  • ux が偶数の場合: ux >> 1
  • ux が奇数の場合: ~(ux >> 1)

このコミットで修正された ReadVarint 関数は、まさにこのジグザグエンコーディングのデコードロジック(x := int64(ux >> 1)if ux&1 != 0 { x = ^x })を含んでいます。

技術的詳細

encoding/binary パッケージの ReadVarint 関数は、io.ByteReader からバイトを読み取り、それを int64 型の符号付き整数としてデコードします。この関数は、内部的に ReadUvarint 関数を呼び出して、まずバイト列を符号なしの uint64 として読み取ります。その後、読み取った uint64 の値に対してジグザグデコーディングを適用し、元の符号付き int64 の値に変換します。

ジグザグデコーディングのロジックは以下の通りです。

  1. ux, err := ReadUvarint(r): まず、バイトリーダー r から符号なし可変長整数 ux を読み取ります。
  2. x := int64(ux >> 1): 読み取った ux を1ビット右シフトし、int64 にキャストします。これはジグザグエンコーディングのデコードの一部で、正の数と負の数の絶対値を復元するステップです。
  3. if ux&1 != 0 { x = ^x }: ux の最下位ビット(LSB)が1であるかどうかをチェックします。LSBが1の場合、元の数値は負の数であったことを示します。この場合、x のビットを反転(1の補数)することで、元の負の数を復元します。

この一連の処理により、ReadVarint は正しく符号付き整数をデコードします。したがって、以前のコメントが「符号なし整数を読み取る」と記述していたのは誤りであり、実際の動作は「符号付き整数を読み取る」ものでした。このコミットは、そのコメントの誤りを修正し、関数の振る舞いを正確に反映させるものです。

コアとなるコードの変更箇所

--- a/src/pkg/encoding/binary/varint.go
+++ b/src/pkg/encoding/binary/varint.go
@@ -123,7 +123,7 @@ func ReadUvarint(r io.ByteReader) (uint64, error) {
 	panic("unreachable")
 }
 
-// ReadVarint reads an encoded unsigned integer from r and returns it as an int64.
+// ReadVarint reads an encoded signed integer from r and returns it as an int64.
 func ReadVarint(r io.ByteReader) (int64, error) {
 	ux, err := ReadUvarint(r) // ok to continue in presence of error
 	x := int64(ux >> 1)

コアとなるコードの解説

変更されたのは、ReadVarint 関数の直前にあるコメント行のみです。

  • - // ReadVarint reads an encoded unsigned integer from r and returns it as an int64.
    • これは変更前のコメントで、「符号なし整数を読み取る」と誤って記述されていました。
  • + // ReadVarint reads an encoded signed integer from r and returns it as an int64.
    • これは変更後のコメントで、「符号付き整数を読み取る」と修正されています。

この変更は、コードの実行ロジックには一切影響を与えません。ReadVarint 関数の実際の動作は、変更前も変更後も一貫して符号付き整数をデコードしていました。この修正は、関数のドキュメントとしてのコメントを、その実際の機能と一致させるためのものです。これにより、この関数を使用する開発者が、その目的と戻り値の型について正確な理解を得られるようになります。

関連リンク

参考にした情報源リンク

このコミットは、Go言語の標準ライブラリ encoding/binary パッケージ内の varint.go ファイルに対する変更です。encoding/binary パッケージは、Goのデータ構造とバイトシーケンス間の変換、特に可変長整数(varint)のエンコードとデコードを提供します。varint.go は、このパッケージ内で可変長整数の読み書きに関する具体的な実装を含んでいます。

コミット

encoding/binary: ReadVarint reads a signed number, not unsigned number

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/f9902c7197f436578e8fafa7946d8cd83467729e

元コミット内容

commit f9902c7197f436578e8fafa7946d8cd83467729e
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Fri Oct 26 21:14:34 2012 +0800

    encoding/binary: ReadVarint reads a signed number, not unsigned number
    
    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/6762051

変更の背景

このコミットの背景は、encoding/binary パッケージの ReadVarint 関数のコメントが、その実際の動作と一致していなかったことです。ReadVarint 関数は、内部的に符号なし整数を読み取る ReadUvarint を利用しつつ、結果として符号付き整数を正しくデコードするロジック(ジグザグエンコーディング)を含んでいました。しかし、既存のコメントは「符号なし整数を読み取る」と誤って記述されており、これが関数の意図や動作を誤解させる可能性がありました。

この変更は、コードの動作自体を変更するものではなく、ドキュメント(コメント)を修正することで、関数の真の目的と動作を正確に反映させ、開発者の混乱を防ぐことを目的としています。これは、コードの可読性と保守性を向上させるための重要なステップです。

前提知識の解説

1. 可変長整数 (Varint)

可変長整数(Varint)は、数値を効率的にエンコードするための手法です。特に、小さい数値は少ないバイト数で表現し、大きい数値はより多くのバイト数で表現することで、全体的なデータサイズを削減します。GoogleのProtocol Buffersなどで広く使用されています。

Varintの基本的な考え方は、各バイトの最上位ビット(MSB)を「継続ビット」として使用することです。MSBが1の場合、その数値は次のバイトにも続くことを示し、MSBが0の場合、そのバイトが数値の最後のバイトであることを示します。残りの7ビットが数値のデータとして使用されます。

2. 符号付き整数と符号なし整数

  • 符号なし整数 (Unsigned Integer): 0以上の整数のみを表現します。すべてのビットが数値の大きさを表すために使用されます。
  • 符号付き整数 (Signed Integer): 正の数、負の数、および0を表現します。通常、最上位ビットが符号ビットとして使用され、0は正、1は負を示します(2の補数表現が一般的)。

3. ジグザグエンコーディング (ZigZag Encoding)

Varintは通常、符号なし整数をエンコードするために設計されています。しかし、負の数をVarintで効率的に表現するためには、ジグザグエンコーディングが使用されます。

ジグザグエンコーディングは、符号付き整数を符号なし整数にマッピングする手法です。これにより、絶対値が小さい負の数(例: -1, -2)も、絶対値が小さい正の数(例: 1, 2)と同様に、Varintで少ないバイト数で表現できるようになります。

マッピングのルールは以下の通りです(例: 32ビット整数):

  • n が正の場合: (n << 1)
  • n が負の場合: (~n << 1) | 1

これにより、以下のようなマッピングが行われます。

  • 0 -> 0
  • -1 -> 1
  • 1 -> 2
  • -2 -> 3
  • 2 -> 4
  • ...

デコード時には、この逆の操作を行います。

  • ux が偶数の場合: ux >> 1
  • ux が奇数の場合: ~(ux >> 1)

このコミットで修正された ReadVarint 関数は、まさにこのジグザグエンコーディングのデコードロジック(x := int64(ux >> 1)if ux&1 != 0 { x = ^x })を含んでいます。

技術的詳細

encoding/binary パッケージの ReadVarint 関数は、io.ByteReader からバイトを読み取り、それを int64 型の符号付き整数としてデコードします。この関数は、内部的に ReadUvarint 関数を呼び出して、まずバイト列を符号なしの uint64 として読み取ります。その後、読み取った uint64 の値に対してジグザグデコーディングを適用し、元の符号付き int64 の値に変換します。

ジグザグデコーディングのロジックは以下の通りです。

  1. ux, err := ReadUvarint(r): まず、バイトリーダー r から符号なし可変長整数 ux を読み取ります。
  2. x := int64(ux >> 1): 読み取った ux を1ビット右シフトし、int64 にキャストします。これはジグザグエンコーディングのデコードの一部で、正の数と負の数の絶対値を復元するステップです。
  3. if ux&1 != 0 { x = ^x }: ux の最下位ビット(LSB)が1であるかどうかをチェックします。LSBが1の場合、元の数値は負の数であったことを示します。この場合、x のビットを反転(1の補数)することで、元の負の数を復元します。

この一連の処理により、ReadVarint は正しく符号付き整数をデコードします。したがって、以前のコメントが「符号なし整数を読み取る」と記述していたのは誤りであり、実際の動作は「符号付き整数を読み取る」ものでした。このコミットは、そのコメントの誤りを修正し、関数の振る舞いを正確に反映させるものです。

コアとなるコードの変更箇所

--- a/src/pkg/encoding/binary/varint.go
+++ b/src/pkg/encoding/binary/varint.go
@@ -123,7 +123,7 @@ func ReadUvarint(r io.ByteReader) (uint64, error) {
 	panic("unreachable")
 }
 
-// ReadVarint reads an encoded unsigned integer from r and returns it as an int64.
+// ReadVarint reads an encoded signed integer from r and returns it as an int64.
 func ReadVarint(r io.ByteReader) (int64, error) {
 	ux, err := ReadUvarint(r) // ok to continue in presence of error
 	x := int64(ux >> 1)

コアとなるコードの解説

変更されたのは、ReadVarint 関数の直前にあるコメント行のみです。

  • - // ReadVarint reads an encoded unsigned integer from r and returns it as an int64.
    • これは変更前のコメントで、「符号なし整数を読み取る」と誤って記述されていました。
  • + // ReadVarint reads an encoded signed integer from r and returns it as an int64.
    • これは変更後のコメントで、「符号付き整数を読み取る」と修正されています。

この変更は、コードの実行ロジックには一切影響を与えません。ReadVarint 関数の実際の動作は、変更前も変更後も一貫して符号付き整数をデコードしていました。この修正は、関数のドキュメントとしてのコメントを、その実際の機能と一致させるためのものです。これにより、この関数を使用する開発者が、その目的と戻り値の型について正確な理解を得られるようになります。

関連リンク

参考にした情報源リンク