[インデックス 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ルーンの分類ロジックの変更です。以前の実装では、minUnified
と maxUnified
、minCompatibility
と maxCompatibility
といったコードポイントの範囲チェックが先行し、その後に 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.go
と colelem_test.go
のテストケースも、この新しいロジックに合わせて期待される値が修正されています。特に builder_test.go
の convLargeTests
では、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文字)に対して、そのプライマリ重みを計算する役割を担っています。
変更前は、この関数はまず minUnified
と maxUnified
、そして minCompatibility
と maxCompatibility
という固定のコードポイント範囲に基づいてルーンを分類していました。これらの範囲に属するルーンは、それぞれ commonUnifiedOffset
を加算してプライマリ重みが計算されていました。これらの範囲に属さないが unicode.Is(unicode.Unified_Ideograph, r)
が true
を返すルーンは、rareUnifiedOffset
を加算して重みが計算されていました。
変更後では、ロジックの順序が入れ替わっています。
- まず、
if unicode.Is(unicode.Ideographic, r)
という条件で、ルーンr
がUnicodeの「表意文字 (Ideographic)」カテゴリに属するかどうかをチェックします。unicode.Ideographic
はunicode.Unified_Ideograph
よりも広範なカテゴリであり、より一般的な表意文字をカバーします。 - この条件が
true
の場合、つまりルーンが表意文字であると確認された場合にのみ、以前のminUnified
/maxUnified
およびminCompatibility
/maxCompatibility
の範囲チェックが実行されます。これにより、コードポイントが単に範囲内にあるだけでなく、Unicode Codepoint Databaseで表意文字として定義されていることが保証されます。 - これらの範囲に属する表意文字は、引き続き
commonUnifiedOffset
を加算して重みが計算されます。 - 上記の範囲には属さないが、
unicode.Is(unicode.Ideographic, r)
がtrue
を返す表意文字は、rareUnifiedOffset
を加算して重みが計算されます。 - 最終的に、どの条件にも合致しないルーンは、
otherOffset
を加算して重みが計算されます。
この変更により、CJKルーンの暗黙の重み付けの計算が、Unicodeの仕様に厳密に準拠するようになりました。特に、回帰テストで発見された、特定のCJKルーンが誤った重み付けをされる問題が解決され、より正確な照合順序が保証されます。
テストファイルの変更は、このロジックの変更によって期待される出力値が変わったことを反映しています。builder_test.go
の convLargeTests
では、0x2F9B2
というコードポイントに対する期待されるプライマリ重みが 0x7F4F2
から 0x4F4F2
に変更されています。これは、implicitPrimary
関数の計算結果が修正されたことを直接示しています。同様に、colelem_test.go
の implicitTests
でも、0xFAFF
に対する期待値が 0x1F63F
から 0x1F619
に変更されており、これも暗黙の重み付けの計算ロジックの修正によるものです。
関連リンク
- Unicode Collation Algorithm (UCA): http://unicode.org/reports/tr10/
- Unicode Character Database (UCD): https://www.unicode.org/ucd/
- Go言語
unicode
パッケージ: https://pkg.go.dev/unicode
参考にした情報源リンク
- コミットメッセージ自体
- Go言語のソースコード(
src/pkg/exp/locale/collate/
ディレクトリ内のファイル) - Unicode Technical Report #10 (Unicode Collation Algorithm)
- Unicode Character Database (UCD) の概念に関する一般的な知識
- Go言語の
unicode
パッケージのドキュメンテーション