是否存在用于将整数限制为0到255的间隔或将整数限制为0.0到1.0的间隔的无分支或类似的技巧? (这两个范围均应封闭,即端点包括在内。)
我正在使用明显的最小-最大检查:
int value = (value < 0? 0 : value > 255? 255 : value);
但是有一种方法可以使它更快-类似于“模”钳位value & 255
?有没有办法用浮点数做类似的事情?
我正在寻找一种便携式解决方案,因此最好不要提供CPU / GPU专用的东西。
这是我用来将int限制在0到255范围内的技巧:
/**
* Clamps the input to a 0 to 255 range.
* @param v any int value
* @return {@code v < 0 ? 0 : v > 255 ? 255 : v}
*/
public static int clampTo8Bit(int v) {
// if out of range
if ((v & ~0xFF) != 0) {
// invert sign bit, shift to fill, then mask (generates 0 or 255)
v = ((~v) >> 31) & 0xFF;
}
return v;
}
那仍然有一个分支,但是很方便的是,您可以通过对它们进行或运算来测试多个整数中的任何一个是否超出范围,在通常情况下,所有它们都位于范围。例如:
/** Packs four 8-bit values into a 32-bit value, with clamping. */
public static int ARGBclamped(int a, int r, int g, int b) {
if (((a | r | g | b) & ~0xFF) != 0) {
a = clampTo8Bit(a);
r = clampTo8Bit(r);
g = clampTo8Bit(g);
b = clampTo8Bit(b);
}
return (a << 24) + (r << 16) + (g << 8) + (b << 0);
}
请注意,如果您对value = min (value, 255)
进行编码,则编译器可能已经提供了您想要的东西。如果存在,则可以将其转换为MIN
指令,也可以转换为带有条件移动的比较,例如x86上的CMOVcc
指令。
以下代码假定整数的二进制补码表示形式,通常在今天已经给出。从布尔到整数的转换不应该在后台进行分支,因为现代体系结构要么提供可直接用于形成掩码的指令(例如x86上的SETcc
,NVIDIA GPU上的ISETcc
),要么可以应用谓词或有条件的举动。如果缺少所有这些,则编译器可以基于Boann的答案,基于算术右移发出无分支指令序列以构造掩码。但是,编译器可能会做一些错误的事情,因此存在一些残留风险,因此,如果有疑问,最好将生成的二进制文件反汇编以进行检查。
int value, mask;
mask = 0 - (value > 255); // mask = all 1s if value > 255, all 0s otherwise
value = (255 & mask) | (value & ~mask);
[在许多体系结构上,使用三元运算符?:
也会导致无分支指令序列。硬件可能支持选择类型的指令,该选择类型的指令本质上与三元运算符等效,例如NVIDIA GPU上的ICMP
。或者它提供x86中的CMOV
(条件移动),或ARM上的[predicate],这两者都可用于为三元运算符实现无分支代码。与前一种情况一样,您可能希望检查反汇编的二进制代码,以确保生成的代码没有分支。
int value;
value = (value > 255) ? 255 : value;
[对于浮点操作数,现代浮点单元通常提供FMIN
和FMAX
指令,它们直接映射到C / C ++标准数学函数fmin()
和fmax()
。或者,可以将fmin()
和fmax()
转换为比较,然后进行条件移动。同样,谨慎检查生成的代码以确保它是无分支的。
double value;
value = fmax (fmin (value, 1.0), 0.0);
我用这个东西,100%无分支。
int clampU8(int val)
{
val &= (val<0)-1; // clamp < 0
val |= -(val>255); // clamp > 255
return val & 0xFF; // mask out
}