(r+1 + (r >> 8)) >> 8 做啥?

Posted

技术标签:

【中文标题】(r+1 + (r >> 8)) >> 8 做啥?【英文标题】:what (r+1 + (r >> 8)) >> 8 does?(r+1 + (r >> 8)) >> 8 做什么? 【发布时间】:2015-05-14 12:35:54 【问题描述】:

在一些旧的 C/C++ 图形相关代码中,我必须移植到 Java 和 javascript,我发现了这个:

b = (b+1 + (b >> 8)) >> 8; // very fast

其中b 是蓝色的short intrb(红色和蓝色)的代码相同。评论没有帮助。

除了明显的移位和添加之外,我无法弄清楚它的作用。我可以不理解就移植,我只是出于好奇而问。

【问题讨论】:

提示:位移 ==> 乘法或除法。 :-) 那你为什么要标记JavaJavascriptCC++ 不了解也可以移植?!这是一项值得珍惜的技能。 在不知道这些颜色值来自何处或为什么要操纵它们的情况下,除非有人碰巧认出了某种诡计,否则可能无法得到答案。 @seanhodges,当然,您可以将代码从一种语言移植到另一种语言,而无需了解所涉及的算法。为什么不呢? 【参考方案1】:
y = ( x + 1 + (x>>8) ) >> 8 // very fast

这是除以 255 的定点近似值。从概念上讲,这对于基于像素值进行归一化计算非常有用,这样 255(通常是最大像素值)正好映射到 1。

它被描述为非常快,因为完全通用的整数除法在许多 CPU 上是一个相对较慢的操作——尽管如果你的编译器可以推导出输入约束。

这是基于257/(256*256) 非常接近1/255 的想法,并且x*257/256 可以表述为x+(x>>8)+1 是舍入支持,它允许公式完全匹配 integer 除法 x/255 用于 [0..65534] 中的所有 x 值。

内部的一些代数可能会使事情更清楚...

       x*257/256
     = (x*256+x)/256
     = x + x/256
     = x + (x>>8)

这里有更多讨论:How to do alpha blend fast? 和这里:Division via Multiplication


顺便说一句,如果您想要四舍五入,并且您的 CPU 可以进行快速乘法运算,那么以下对于所有 uint16_t 被除数值都是准确的——实际上是 [0..(2^16)+126]。

y = ((x+128)*257)>>16 // divide by 255 with round-to-nearest for x in [0..65662]

【讨论】:

P.S.由于gamma compression 引入的非线性映射,对已以 8 位编码的像素进行图像处理通常是不正确的(但通常足够接近)。 与“快速乘法”相关的注释:***.com/q/6357038【参考方案2】:

看起来它是为了检查 blue(或 redgreen)是否被充分使用。当b255 并且对于所有较低的值是0 时,它的计算结果为1

【讨论】:

如果 b 介于 1 和 255 之间,这是有道理的。但是(b >> 8) 总是为零……写(b+1) >> 8 就足够了。 不过,这个值很短。最大值为 65535(无符号)。 这是最大的物理值。也许有一个代码强制执行的最大逻辑值。【参考方案3】:

当您想要使用比 257/256 更准确的公式时,一个常见的用例是您必须将每个像素的大量 alpha 值组合在一起。举个例子,在进行图像缩小时,您需要为对目标有贡献的每个源像素组合 4 个 alpha,然后对对目标有贡献的所有源像素进行组合。

我发布了 /255 的无限准确位旋转版本,但被无故拒绝。所以我要补充一点,我以实现 alpha 混合硬件为生,我以编写实时图形代码和游戏引擎为生,我已经在 MICRO 等会议上发表过关于这个主题的文章,所以我真的知道我在做什么谈论。并且对于人们来说理解精确为 1/255 的更准确的公式可能会很有用或至少很有趣:

版本 1:x = (x + (x >> 8)) >> 8 - 没有添加常数,不满足 (x * 255) / 255 = x,但在大多数情况下看起来不错。 版本 2:x = (x + (x >> 8) + 1) >> 8 - 将满足 (x * 255) / 255 = x 对于整数,但不会为所有 alpha 命中正确的整数值

版本 3:(简单整数舍入): (x + (x >> 8) + 128) >> 8 - 不会为所有 alpha 命中正确的整数值,但平均而言会以相同的成本比版本 2 更接近。

第 4 版:无限精确的版本,可达到所需的任何精度水平,适用于任意数量的复合 alpha:(用于调整图像大小、旋转等):

[(x + (x >> 8)) >> 8] + [ ( (x & 255) + (x >> 8) ) >> 8]

为什么版本 4 无限准确? 因为 1/255 = 1/256 + 1/65536 + 1/256^3 + 1/256^4 + ...

上面最简单的表达式(版本 1)不处理四舍五入,但它也不处理从无限数量的相同总和列中出现的进位。上面添加的新术语确定了这个无限数量的基数 256 位的进位(0 或 1)。通过添加它,您将获得与添加所有无限加数相同的结果。此时,您可以通过将半位添加到您想要的任何精度点来进行舍入。

也许 OP 不需要,但人们应该知道你根本不需要近似。上面的公式其实比双精度浮点更准确。

至于速度:在硬件中,这种方法甚至比单个(全宽)添加还要快。在软件中,您必须考虑吞吐量与延迟。在延迟方面,它可能仍然比窄乘法更快(肯定比全宽度乘法更快),但在 OP 上下文中,您可以一次展开许多像素,并且由于现代乘法单元是流水线的,所以您仍然可以。在翻译成 Java 时,您可能没有窄乘法,所以这仍然可以更快,但需要检查。

WRT 有人说“为什么不使用内置的操作系统功能进行 alpha blitting?”:如果您已经在该操作系统中拥有大量图形代码库,那么这可能是一个不错的选择。如果没有,您将查看成百上千行代码来利用操作系统版本——这些代码比此代码更难编写和调试。最后,您拥有的操作系​​统代码根本不可移植,而此代码可以在任何地方使用。

【讨论】:

拒绝您对我的帖子进行编辑的原因之一是“此编辑偏离了帖子的原始意图。即使是必须进行重大更改的编辑也应努力维护帖子所有者的目标。”换句话说,合适的做法是写你自己的帖子,就像你现在所做的那样。 啊……我明白了。谢谢。 :)【参考方案4】:

我怀疑它正在尝试执行以下操作:

boolean isBFullyOn = false;

if (b == 0xff) 
  isBFullyOn = true;

回到处理器速度慢的时代;像上面这样的智能位移技巧可能比明显的 if-then-else 逻辑更快。它避免了代价高昂的跳转语句。

它可能还在处理器中设置了一个溢出标志,用于后面的一些逻辑。这一切都高度依赖于目标处理器。

我也是投机的!!

【讨论】:

bool isFullyOn = b == 0xFF 会更快(当然假设没有编译器优化。) 在 Java 中这是正确的;但是 OP 正在移植一些旧的 C/C++ 图形代码,这些代码很可能被编译为针对特定 CPU,这将导致高性能的汇编代码。 @Dave 您认为快速的内容实际上是我在上面发布的代码的语法糖。它意味着汇编代码中的跳转。 是的。我发帖后不久就意识到了这一点。我想这个问题的真正意义在于,破解编译器生成所需代码的程序员应该被要求记录:他们试图做什么,为什么他们认为它有效,以及他们测试过的编译器版本。 查看@nobar 发布的链接。这些类型的优化很常见。【参考方案5】:

b+1 + b/256的值,这个计算除以256

这样,使用移位编译器使用 CPU 级移位指令进行编译,而不是使用 FPU 或库除法函数。

【讨论】:

这是真的,但它并没有真正解决代码执行该操作的原因。 我认为 OP 完全知道>> 8/ 256。他们要问的是为什么这个表达式正在被完成,也就是说,它在语义上是什么意思。【参考方案6】:

b = (b + (b >> 8)) >> 8; 基本上是b = b *257/256

我认为+1 是对-0.5 的丑陋黑客手段,由内部>>8 引起。

我会把它写成b = (b + 128 + ((b +128)>> 8)) >> 8;

【讨论】:

你的等价是关闭的(应该是b*257/256/256),但是看到你的回答让我认识到这个公式。谢谢。【参考方案7】:

运行此测试代码:

public void test() 
    Set<Integer> results = new HashSet<Integer>();
    // short int ranges between -32767 and 32767
    for (int i = -32767; i <= 32767; i++) 
        int b = (i + 1 + (i >> 8)) >> 8;
        if (!results.contains(b)) 
            System.out.println(i + " -> " + b);
            results.add(b);
        
    

产生-129128 之间的所有可能值。但是,如果您使用 8 位颜色 (0 - 255),那么唯一可能的输出是 0(对于 0 - 254)和 1(对于 255),因此它很可能正在尝试函数@kaykay posted.

【讨论】:

以上是关于(r+1 + (r >> 8)) >> 8 做啥?的主要内容,如果未能解决你的问题,请参考以下文章

162. 寻找峰值

计算BGR平均色

平面,半平面和交错格式有什么区别。

R学习-8.Logic

cf478d 线性dp好题

python网络爬虫