源碼
// Calculates floor(a*b/255 + 0.5)
#[inline]
pub fn muldiv255(a: u32, b: u32) -> u32 {// The deriviation for this formula can be// found in "Three Wrongs Make a Right" by Jim Blinn.let tmp = a * b + 128;(tmp + (tmp >> 8)) >> 8
}
代碼分析
這個 muldiv255 函數是一個高效的實現,用于計算 floor(a * b / 255 + 0.5),也就是將兩個無符號整數 a 和 b 相乘,然后除以 255,并進行四舍五入。這種計算在圖像處理中很常見,比如在 alpha 混合(alpha blending)或顏色空間轉換時。
步驟分解:
- a * b + 128:
- 首先計算 a * b,然后加上 128。這個 128 的作用是為了后續的四舍五入(因為 255 / 2 = 127.5,所以加 128 可以確保正確的舍入)。
- tmp + (tmp >> 8):
-
tmp >> 8 相當于 tmp / 256(右移 8 位相當于除以 2^8)。
-
然后 tmp + (tmp >> 8) 相當于 tmp + tmp / 256。
-
8:
- 最后右移 8 位,相當于除以 256。
為什么這個公式有效?
這個方法的數學原理來自 Jim Blinn 的文章 “Three Wrongs Make a Right”,它利用了近似計算來避免昂貴的除法運算(因為整數除法在 CPU 上通常比乘法和移位慢)。
-
原本的計算是 (a * b + 127) / 255(四舍五入)。
-
但 (a * b + 128) * 257 / 65536(即 (tmp + (tmp >> 8)) >> 8)是一個近似計算,可以高效地用移位和加法實現。
數學推導:
-
我們希望計算 (a * b + 127) / 255(四舍五入)。
-
可以近似為 (a * b + 128) * (256 + 1) / 65536(因為 255 ≈ 65536 / 257)。
展開后:
(a * b + 128) * (256 + 1) / 65536
= (256*(a*b + 128) + (a*b + 128)) / 65536
= ( (a*b + 128) << 8 + (a*b + 128) ) >> 16
= (tmp << 8 + tmp) >> 16
= (tmp + (tmp >> 8)) >> 8
最終得到 (tmp + (tmp >> 8)) >> 8。
示例
假設 a = 100, b = 200:
-
tmp = 100 * 200 + 128 = 20000 + 128 = 20128
-
tmp >> 8 = 20128 / 256 = 78
-
tmp + (tmp >> 8) = 20128 + 78 = 20206
20206 >> 8 = 20206 / 256 = 78(實際 100 * 200 / 255 ≈ 78.431,四舍五入得 78)
為什么不用 (a * b + 127) / 255?
雖然 (a * b + 127) / 255 也能得到正確結果,但:
-
除法 (/ 255) 在 CPU 上通常比移位和加法慢。
-
這個優化方法避免了除法,改用移位和加法,性能更高。
適用場景
-
圖像處理(如 alpha 混合、顏色計算)。
-
需要高效計算 (a * b) / 255 并四舍五入的情況。
總結
這個函數巧妙地利用移位和加法來近似計算 (a * b) / 255 并四舍五入,避免了昂貴的除法運算,是一種高效的位操作優化方法。