源码
rust
// 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 并四舍五入,避免了昂贵的除法运算,是一种高效的位操作优化方法。