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

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

このコミットは、Go言語の実験的なロケールパッケージ exp/locale/collate における、CJK (中国語、日本語、韓国語) 文字の照合順序に関するバグ修正と改善を扱っています。具体的には、Unicodeの照合アルゴリズムにおける「暗黙の重み付け (Implicit Weights)」の計算方法が、Unicode Codepoint Databaseの定義と整合するように修正されました。

コミット

commit 56a76c88f8ff1d0b46168512b370e5a48b8ee2a8
Author: Marcel van Lohuizen <mpvl@golang.org>
Date:   Mon May 7 11:51:40 2012 +0200

    exp/locale/collate: from the regression test we derive that the spec
    dictates a CJK rune is only part of a certain specified range if it
    is explicitly defined in the Unicode Codepoint Database.
    Fixed the code and some of the tests accordingly.
    
    R=r
    CC=golang-dev
    https://golang.org/cl/6160044

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

https://github.com/golang/go/commit/56a76c88f8ff1d0b46168512b370e5a48b8ee2a8

元コミット内容

このコミットは、回帰テストから得られた知見に基づき、CJKルーン(文字)が特定の範囲に属するかどうかの判断は、Unicode Codepoint Databaseで明示的に定義されている場合に限られるという仕様に準拠するようにコードを修正しました。これに伴い、関連するテストも調整されています。

変更の背景

Go言語の exp/locale/collate パッケージは、国際化対応の一環として、異なる言語や地域における文字列のソート順序(照合順序)を扱うための実験的な機能を提供していました。特に、CJK文字のような膨大な数の文字を持つスクリプトでは、全ての文字に明示的な照合順序を定義することが非現実的であるため、Unicode Collation Algorithm (UCA) は「暗黙の重み付け (Implicit Weights)」というメカニズムを導入しています。これは、明示的に定義されていない文字に対して、そのUnicodeコードポイントに基づいて自動的に照合重みを割り当てるものです。

このコミットの背景には、既存の回帰テストが、CJKルーンが特定の範囲(例えば、Unified Ideographsの範囲)に属するかどうかの判断基準が、Unicodeの仕様と異なる振る舞いをしていたという問題があります。Unicodeの仕様では、ある文字が特定のカテゴリ(例: Unified Ideograph)に属すると判断されるのは、その文字がUnicode Codepoint Databaseに明示的にそのカテゴリとして定義されている場合に限られます。しかし、Goの exp/locale/collate パッケージの実装では、この厳密な定義が守られていなかった可能性があります。その結果、誤った暗黙の重み付けが計算され、CJK文字のソート順序が期待通りにならないというバグが発生していました。

このコミットは、この不整合を解消し、Unicodeの厳密な仕様に準拠することで、CJK文字の照合順序の正確性を向上させることを目的としています。

前提知識の解説

Unicode Collation Algorithm (UCA)

UCAは、Unicode文字列を言語的に正しい順序でソートするための国際標準アルゴリズムです。異なる言語や地域には独自のソート規則があるため、UCAはこれらの規則を柔軟にサポートします。UCAは、各文字に複数の「照合要素 (Collation Element)」を割り当て、それらの要素の重み(プライマリ、セカンダリ、ターシャリなど)を比較することでソート順を決定します。

暗黙の重み付け (Implicit Weights)

UCAでは、全てのUnicode文字に対して明示的な照合要素を定義する代わりに、特にCJK文字のような膨大な数の文字を持つスクリプトのために「暗黙の重み付け」のメカニズムを提供しています。これは、Unicodeの特定のブロック(例: CJK Unified Ideographs)に属する文字で、かつUnicode Collation AlgorithmのDefault Unicode Collation Element Table (DUCET) に明示的な照合要素が定義されていないものに対して、そのコードポイントに基づいて自動的に照合重みを割り当てる方法です。これにより、DUCETのサイズを管理しつつ、未定義の文字も適切にソートできます。

Unicode Codepoint Database

Unicode Codepoint Database (UCD) は、Unicode標準の核となるデータリポジトリです。各Unicodeコードポイントに関する様々なプロパティ(カテゴリ、スクリプト、名前、双方向性プロパティなど)が定義されています。文字が特定のカテゴリ(例: Unified_Ideograph)に属するかどうかは、このデータベースの定義に基づいています。

Go言語の unicode パッケージ

Go言語の標準ライブラリには unicode パッケージが含まれており、Unicode文字のプロパティを扱うための機能を提供します。例えば、unicode.Is(rangeTab *RangeTable, r rune) 関数は、指定されたルーン r が特定のUnicodeカテゴリ(RangeTableで定義される)に属するかどうかを判定します。このコミットでは、unicode.Is(unicode.Unified_Ideograph, r) の代わりに unicode.Is(unicode.Ideographic, r) を使用する変更が見られますが、これはより広範な表意文字のカテゴリを指す可能性があります。

技術的詳細

このコミットの核心は、implicitPrimary 関数におけるCJKルーンの分類ロジックの変更です。以前の実装では、minUnifiedmaxUnifiedminCompatibilitymaxCompatibility といったコードポイントの範囲チェックが先行し、その後に unicode.Is(unicode.Unified_Ideograph, r) によるチェックが行われていました。

変更後のコードでは、まず unicode.Is(unicode.Ideographic, r) を用いて、ルーンが「表意文字 (Ideographic)」であるかをチェックします。このチェックが true を返した場合にのみ、minUnified / maxUnified および minCompatibility / maxCompatibility の範囲チェックが行われるようになりました。

この変更の意図は、Unicodeの仕様、特にTR10 (Unicode Collation Algorithm) の「Implicit Weights」セクションで述べられているように、「CJKルーンが特定の指定された範囲の一部であると見なされるのは、それがUnicode Codepoint Databaseで明示的に定義されている場合に限られる」という原則を厳密に適用することにあります。

つまり、単にコードポイントが特定の範囲内にあるというだけでなく、そのコードポイントが実際にUnicodeの「表意文字」として定義されているかどうかの確認を優先することで、より正確な暗黙の重み付けの計算を実現しています。これにより、回帰テストで発見された、仕様との不整合が解消されます。

また、builder_test.gocolelem_test.go のテストケースも、この新しいロジックに合わせて期待される値が修正されています。特に builder_test.goconvLargeTests では、cjk(0x2F9B2) の期待値が pqCE(0x7F4F2, 0x2F9B2) から pqCE(0x4F4F2, 0x2F9B2) に変更されており、これは暗黙の重み付けの計算結果が変わったことを示しています。

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

src/pkg/exp/locale/collate/build/colelem.go および src/pkg/exp/locale/collate/colelem.go

--- a/src/pkg/exp/locale/collate/build/colelem.go
+++ b/src/pkg/exp/locale/collate/build/colelem.go
@@ -162,16 +162,16 @@ const (
 // http://unicode.org/reports/tr10/#Implicit_Weights,
 // but preserve the resulting relative ordering of the runes.
 func implicitPrimary(r rune) int {
-	if r >= minUnified && r <= maxUnified {
-		// The most common case for CJK.
-		return int(r) + commonUnifiedOffset
-	}
-	if r >= minCompatibility && r <= maxCompatibility {
-		// This will never hit as long as we don't remove the characters
-		// that would match from the table.
-		return int(r) + commonUnifiedOffset
-	}
-	if unicode.Is(unicode.Unified_Ideograph, r) {
+	if unicode.Is(unicode.Ideographic, r) {
+		if r >= minUnified && r <= maxUnified {
+			// The most common case for CJK.
+			return int(r) + commonUnifiedOffset
+		}
+		if r >= minCompatibility && r <= maxCompatibility {
+			// This will typically not hit. The DUCET explicitly specifies mappings
+			// for all characters that do not decompose.
+			return int(r) + commonUnifiedOffset
+		}
 		return int(r) + rareUnifiedOffset
 	}
 	return int(r) + otherOffset

src/pkg/exp/locale/collate/colelem.go も同様の変更が加えられています。

src/pkg/exp/locale/collate/build/builder_test.go

--- a/src/pkg/exp/locale/collate/build/builder_test.go
+++ b/src/pkg/exp/locale/collate/build/builder_test.go
@@ -63,7 +63,7 @@ type convertTest struct {
 
 var convLargeTests = []convertTest{
 	{pCE(0xFB39), pCE(0xFB39), false},
-	{cjk(0x2F9B2), pqCE(0x7F4F2, 0x2F9B2), false},
+	{cjk(0x2F9B2), pqCE(0x4F4F2, 0x2F9B2), false},
 	{pCE(0xFB40), pCE(0), true},
 	{append(pCE(0xFB40), pCE(0)[0]), pCE(0), true},
 	{pCE(0xFFFE), pCE(illegalOffset), false},

src/pkg/exp/locale/collate/colelem_test.go

--- a/src/pkg/exp/locale/collate/colelem_test.go
+++ b/src/pkg/exp/locale/collate/colelem_test.go
@@ -141,7 +141,7 @@ var implicitTests = []implicitTest{
 	{0xF8FF, 0x5F43F},
 	{0xF900, 0x1F440},
 	{0xFA23, 0x1F563},
-	{0xFAFF, 0x1F63F},
+	{0xFAD9, 0x1F619},
 	{0xFB00, 0x5F640},
 	{0x20000, 0x3FB40},
 	{0x2B81C, 0x4B35C},

コアとなるコードの解説

implicitPrimary 関数は、Unicodeの照合アルゴリズムにおいて、明示的な照合要素が定義されていないルーン(主にCJK文字)に対して、そのプライマリ重みを計算する役割を担っています。

変更前は、この関数はまず minUnifiedmaxUnified、そして minCompatibilitymaxCompatibility という固定のコードポイント範囲に基づいてルーンを分類していました。これらの範囲に属するルーンは、それぞれ commonUnifiedOffset を加算してプライマリ重みが計算されていました。これらの範囲に属さないが unicode.Is(unicode.Unified_Ideograph, r)true を返すルーンは、rareUnifiedOffset を加算して重みが計算されていました。

変更後では、ロジックの順序が入れ替わっています。

  1. まず、if unicode.Is(unicode.Ideographic, r) という条件で、ルーン r がUnicodeの「表意文字 (Ideographic)」カテゴリに属するかどうかをチェックします。unicode.Ideographicunicode.Unified_Ideograph よりも広範なカテゴリであり、より一般的な表意文字をカバーします。
  2. この条件が true の場合、つまりルーンが表意文字であると確認された場合にのみ、以前の minUnified / maxUnified および minCompatibility / maxCompatibility の範囲チェックが実行されます。これにより、コードポイントが単に範囲内にあるだけでなく、Unicode Codepoint Databaseで表意文字として定義されていることが保証されます。
  3. これらの範囲に属する表意文字は、引き続き commonUnifiedOffset を加算して重みが計算されます。
  4. 上記の範囲には属さないが、unicode.Is(unicode.Ideographic, r)true を返す表意文字は、rareUnifiedOffset を加算して重みが計算されます。
  5. 最終的に、どの条件にも合致しないルーンは、otherOffset を加算して重みが計算されます。

この変更により、CJKルーンの暗黙の重み付けの計算が、Unicodeの仕様に厳密に準拠するようになりました。特に、回帰テストで発見された、特定のCJKルーンが誤った重み付けをされる問題が解決され、より正確な照合順序が保証されます。

テストファイルの変更は、このロジックの変更によって期待される出力値が変わったことを反映しています。builder_test.goconvLargeTests では、0x2F9B2 というコードポイントに対する期待されるプライマリ重みが 0x7F4F2 から 0x4F4F2 に変更されています。これは、implicitPrimary 関数の計算結果が修正されたことを直接示しています。同様に、colelem_test.goimplicitTests でも、0xFAFF に対する期待値が 0x1F63F から 0x1F619 に変更されており、これも暗黙の重み付けの計算ロジックの修正によるものです。

関連リンク

参考にした情報源リンク

  • コミットメッセージ自体
  • Go言語のソースコード(src/pkg/exp/locale/collate/ ディレクトリ内のファイル)
  • Unicode Technical Report #10 (Unicode Collation Algorithm)
  • Unicode Character Database (UCD) の概念に関する一般的な知識
  • Go言語の unicode パッケージのドキュメンテーション