|
| 1 | +# Float32 Precision Issue Analysis |
| 2 | + |
| 3 | +## 報告された問題 |
| 4 | + |
| 5 | +float32への丸め込みで偽陰性(false negative)で重なりが検出されないという報告。 |
| 6 | + |
| 7 | +## 調査結果 |
| 8 | + |
| 9 | +### コードベースの分析 |
| 10 | + |
| 11 | +#### 1. 内部表現 |
| 12 | +- すべてのバウンディングボックスは内部的に`Real`型(=`float`)で保存される |
| 13 | +- `include/prtree/core/detail/bounding_box.h:17`で定義: `using Real = float;` |
| 14 | + |
| 15 | +#### 2. 精度補正メカニズム(float64入力時のみ) |
| 16 | + |
| 17 | +**float64入力の場合** (`include/prtree/core/prtree.h:213-291`): |
| 18 | +```cpp |
| 19 | +// Constructor for float64 input (float32 tree + double refinement) |
| 20 | +PRTree(const py::array_t<T> &idx, const py::array_t<double> &x) |
| 21 | +``` |
| 22 | +- 内部的にfloat32に変換してツリーを構築 |
| 23 | +- しかし、元のdouble精度の座標を`idx2exact`に保存(line 274) |
| 24 | +- クエリ時に`refine_candidates()`メソッド(line 805-831)でdouble精度で再チェック |
| 25 | +
|
| 26 | +**float32入力の場合** (`include/prtree/core/prtree.h:138-210`): |
| 27 | +```cpp |
| 28 | +// Constructor for float32 input (no refinement, pure float32 performance) |
| 29 | +PRTree(const py::array_t<T> &idx, const py::array_t<float> &x) |
| 30 | +``` |
| 31 | +- float32のまま処理 |
| 32 | +- `idx2exact`は空のまま(line 157のコメント: "idx2exact is NOT populated for float32 input") |
| 33 | +- **補正が行われない** |
| 34 | + |
| 35 | +#### 3. 交差判定ロジック |
| 36 | + |
| 37 | +`include/prtree/core/detail/bounding_box.h:106-125`: |
| 38 | +```cpp |
| 39 | +bool operator()(const BB &target) const { |
| 40 | + // ... (省略) |
| 41 | + for (int i = 0; i < D; ++i) { |
| 42 | + flags[i] = -minima[i] <= maxima[i]; // 閉区間セマンティクス |
| 43 | + } |
| 44 | + // ... |
| 45 | +} |
| 46 | +``` |
| 47 | +
|
| 48 | +- すべてfloat32で計算される |
| 49 | +- `<=`を使用(touching boxes are considered intersecting) |
| 50 | +
|
| 51 | +#### 4. query_intersections()メソッド |
| 52 | +
|
| 53 | +`include/prtree/core/prtree.h:894-1040`: |
| 54 | +- line 963-965と993-996で、`idx2exact`が空でない場合のみ補正を行う |
| 55 | +- **float32入力の場合は補正がスキップされる**(line 808-810) |
| 56 | +
|
| 57 | +### 理論的な脆弱性 |
| 58 | +
|
| 59 | +1. **丸め込みによる精度損失**: |
| 60 | + - float64で表現された微小な重なりがfloat32に変換される際に失われる可能性 |
| 61 | + - 特に大きな座標値(例: 10^7以上)では、float32のULP(Unit in Last Place)が大きくなる |
| 62 | +
|
| 63 | +2. **補正メカニズムの非対称性**: |
| 64 | + - float64入力: float32ツリー + double補正 |
| 65 | + - float32入力: float32ツリーのみ(補正なし) |
| 66 | + - これにより、同じデータでも入力型によって結果が異なる可能性 |
| 67 | +
|
| 68 | +3. **潜在的な偽陰性シナリオ**: |
| 69 | + - 2つのAABBが非常に小さな量で重なる |
| 70 | + - その重なりがfloat32の精度限界以下 |
| 71 | + - float32に丸められた後、重ならなくなる |
| 72 | +
|
| 73 | +### テスト結果 |
| 74 | +
|
| 75 | +複数のテストケースを作成して検証を試みましたが、実際の偽陰性を再現することはできませんでした: |
| 76 | +
|
| 77 | +1. **test_float32_overlap_issue.py**: 基本的な精度テスト |
| 78 | +2. **test_float32_refined.py**: より厳密な精度境界テスト |
| 79 | +3. **test_float32_extreme.py**: 極端なケース(ULP境界、負の座標、subnormal値など) |
| 80 | +
|
| 81 | +#### テスト結果の考察 |
| 82 | +
|
| 83 | +すべてのテストで偽陰性は検出されませんでした。理由として考えられるのは: |
| 84 | +
|
| 85 | +1. **閉区間セマンティクス**: `<=`比較により、境界で接触するボックスは常に交差と判定される |
| 86 | +2. **一貫した丸め込み**: 両方のボックスが同じ精度(float32)で保存されるため、比較は一貫している |
| 87 | +3. **テストケースの限界**: 実際の報告された問題を再現する特定のデータセットが必要な可能性 |
| 88 | +
|
| 89 | +## 問題の根本原因(理論的分析) |
| 90 | +
|
| 91 | +報告された偽陰性の問題は、以下の条件で発生する可能性があります: |
| 92 | +
|
| 93 | +### ケース1: 異なる精度での構築とクエリ |
| 94 | +
|
| 95 | +```python |
| 96 | +# float32でツリーを構築 |
| 97 | +boxes_f32 = np.array([[0.0, 0.0, 100.0, 100.0]], dtype=np.float32) |
| 98 | +tree = PRTree2D(np.array([0]), boxes_f32) |
| 99 | +
|
| 100 | +# float64でクエリ(わずかに異なる境界値) |
| 101 | +query_f64 = np.array([100.0 + epsilon, 0.0, 200.0, 100.0], dtype=np.float64) |
| 102 | +result = tree.query(query_f64) # 偽陰性の可能性 |
| 103 | +``` |
| 104 | + |
| 105 | +### ケース2: 大きな座標値での微小な重なり |
| 106 | + |
| 107 | +float32の精度限界: |
| 108 | +- 100付近: ULP ≈ 7.6e-6 |
| 109 | +- 10,000付近: ULP ≈ 0.000977 |
| 110 | +- 1,000,000付近: ULP ≈ 0.0625 |
| 111 | +- 16,777,216 (2^24)付近: ULP = 2.0 |
| 112 | + |
| 113 | +大きな座標値では、微小な重なりが丸め込みで失われやすい。 |
| 114 | + |
| 115 | +### ケース3: 累積丸め込みエラー |
| 116 | + |
| 117 | +複数の演算を経た座標値は、累積的な丸め込みエラーにより、 |
| 118 | +元の値から大きくずれる可能性がある。 |
| 119 | + |
| 120 | +## 推奨される対策 |
| 121 | + |
| 122 | +1. **float64入力の使用を推奨**: |
| 123 | + - float64入力を使用すれば、内部補正メカニズムにより高精度が保たれる |
| 124 | + |
| 125 | +2. **float32入力にも補正メカニズムを追加**: |
| 126 | + - float32で構築されたツリーでも、クエリ時にはdouble精度で再チェック |
| 127 | + - ただし、元の精度情報が失われているため完全な補正は不可能 |
| 128 | + |
| 129 | +3. **ドキュメントの改善**: |
| 130 | + - float32使用時の精度制限を明示的に文書化 |
| 131 | + - 高精度が必要な場合はfloat64の使用を推奨 |
| 132 | + |
| 133 | +4. **精度警告の追加**: |
| 134 | + - 大きな座標値(>10^6)でfloat32を使用する場合、警告を表示 |
| 135 | + |
| 136 | +## 検証スクリプト |
| 137 | + |
| 138 | +以下の3つのテストスクリプトを作成しました: |
| 139 | + |
| 140 | +1. `test_float32_overlap_issue.py`: 基本的な偽陰性テスト |
| 141 | +2. `test_float32_refined.py`: より厳密な精度境界テスト |
| 142 | +3. `test_float32_extreme.py`: 極端なエッジケーステスト |
| 143 | + |
| 144 | +実行方法: |
| 145 | +```bash |
| 146 | +python test_float32_overlap_issue.py |
| 147 | +python test_float32_refined.py |
| 148 | +python test_float32_extreme.py |
| 149 | +``` |
| 150 | + |
| 151 | +## 結論 |
| 152 | + |
| 153 | +1. **コードレベルでの脆弱性確認**: float32入力時の補正メカニズムの欠如を確認 |
| 154 | +2. **実際の偽陰性の再現**: テストケースでは再現できず |
| 155 | +3. **理論的なリスク**: 特定の条件下(大きな座標値、微小な重なり)で偽陰性が発生する可能性あり |
| 156 | +4. **推奨事項**: 高精度が必要な場合はfloat64入力を使用すること |
| 157 | + |
| 158 | +この問題を完全に解決するには、報告者から具体的なデータセットや再現手順の提供が必要です。 |
0 commit comments