1. 問題背景
在嵌入式裝置中,雖然可能配備單精度浮點運算單元 (FPU),但標準 C 語言函式庫的 `asin()`
函數通常使用雙精度浮點數來實作。這會導致在硬體上進行成本高昂的軟體模擬計算,進而影響 UWB (超寬頻)
通訊等應用的即時性要求。
因此,我們採用 查表法 (Lookup Table, LUT) 來取代動態的浮點運算,這是一種典型的「以記憶體換取速度」的策略,能顯著提升執行效能。
2. 輸入與輸出條件
輸入範圍
- `arcsin` 的標準輸入:[-1.0, 1.0]
- 為避免浮點運算,輸入值會先乘上一個固定的解析度 (Q9.7 格式的小數部分,即 2⁷ = 128)。
輸出範圍
- `arcsin` 的標準輸出:[-90°, 90°]
- 使用 Q9.7 格式,角度值存放於 `int16_t`,範圍為 [-11520, 11520] (因為 90 × 128 = 11520)。
3. LUT 的對稱性優化
`arcsin(x)` 函數在 0 點是奇函數,滿足 arcsin(-x) = -arcsin(x) 的對稱特性。利用這一點,我們只需儲存正區間 [0.0, 1.0] 的對應角度,就可以透過取相反數來推導負區間的結果。這項優化能將 LUT 的尺寸減少一半。
LUT 總尺寸 = 129 個 `int16_t` 條目 (索引 0 至 128) × 2 位元組 = 258 位元組。
4. LUT 建立方法
建立 LUT 的步驟如下:
- 決定輸入解析度:Q9.7 格式提供 128 個步階。每個索引對應的輸入值為 `index / 128.0`。
- 計算對應角度:遍歷所有索引,計算其對應的角度值,並轉換為 Q9.7 格式儲存。
for (int i = 0; i <= 128; i++) {
double input = (double)i / 128.0; // 正規化輸入 [0.0, 1.0]
double angle = asin(input) * 180.0 / M_PI; // 弧度轉角度
arcSinToDegLut[i] = (int16_t)(angle * 128); // 轉為 Q9.7 格式並儲存
}
使用時,若輸入值為正,則直接查表;若為負,則先取其絕對值查表,再將結果取相反數。
5. 範例
索引 (Index) | 輸入值 (x) | arcsin(x) (°) | 存放值 (Q9.7) |
---|---|---|---|
0 | 0.0 | 0.0 | 0 |
43 | ~0.34 | ~19.625 | 2512 |
64 | 0.5 | 30.0 | 3840 |
128 | 1.0 | 90.0 | 11520 |
6. 優缺點
優點
- 避免昂貴的雙精度浮點計算。
- 執行速度極快,適合即時系統。
- 記憶體佔用極低 (258 位元組)。
缺點
- 存在運算誤差 (使用就近取整法時最大約 3.58°)。
- 增加解析度或改用插值法可降低誤差,但會增加資源消耗。
7. 誤差來源、策略與權衡
LUT 的精度與其實作方式密切相關,尤其取決於查表索引策略。誤差主要來自於量化誤差(將連續輸入對應到離散索引)和 `arcsin` 函數本身的非線性特性(在接近 ±1.0 時斜率急遽增加,放大誤差)。
7.1 解析度、索引策略與理論誤差上限
不同的索引策略會產生截然不同的誤差上限。本文件的模擬器採用的是就近取整 (Round) 策略,其理論誤差上限約為兩點之間最大角度差的一半。
條目數 | 記憶體 | 索引策略 | 理論最大誤差 (°) |
---|---|---|---|
129 (Q9.7) | 258 B | 就近取整 (Round) | ~3.58° |
線性插值 | ~1.79° | ||
257 (Q8.8) | 514 B | 就近取整 (Round) | ~2.53° |
線性插值 | ~1.27° | ||
513 (Q7.9) | 1026 B | 就近取整 (Round) | ~1.79° |
線性插值 | ~0.90° |
7.2 進階優化:線性插值 (Linear Interpolation)
從上表可見,線性插值是 CP 值極高的優化方案。它不是直接選取最接近的點,而是在兩個相鄰的 LUT 點之間,根據輸入值的位置進行加權平均。這能以極小的計算成本(幾次加減乘除)大幅降低誤差,非常適合嵌入式系統。
其他更複雜的優化方法還包括使用非等距節點(在曲線陡峭處增加取樣點密度)或在極端區間使用多項式近似等,以在有限的記憶體下達到更高的精度。
LUT 產生程式 (C 語言)
這段 C 程式可以在個人電腦上執行,以生成 `arcSinToDegLut[]` 陣列的完整內容。您可以將其輸出結果直接複製到嵌入式裝置的韌體中使用。
#include <stdio.h>
#include <math.h>
#include <stdint.h>
#define Q_FORMAT 128 // Q9.7 -> 2^7 = 128
#define TABLE_SIZE 128 // 輸入 [0.0 .. 1.0] 共 128 個步階
int main(void) {
printf("static const int16_t arcSinToDegLut[%d] = {\n", TABLE_SIZE + 1);
for (int i = 0; i <= TABLE_SIZE; i++) {
double input = (double)i / TABLE_SIZE;
double angle = asin(input) * 180.0 / M_PI;
int16_t fixedVal = (int16_t)round(angle * Q_FORMAT);
if (i % 8 == 0) printf(" ");
printf("%6d,", fixedVal);
if (i % 8 == 7 || i == TABLE_SIZE) printf("\n");
}
printf("};\n");
return 0;
}
綜合實踐:互動式 LUT 模擬器
在了解上述原理後,您可以使用下方的滑桿來模擬 `arcsin` 函數的查表過程(採用就近取整法),並與標準函式庫的精確結果進行比較。
LUT 結果
-
標準 `asin` 答案
-
誤差比較
-