将整数限制为 0-255 并加倍到 0.0-1.0 的技巧?
Hacks for clamping integer to 0-255 and doubles to 0.0-1.0?
是否有任何无分支或类似的技巧可以将整数限制在 0 到 255 的区间内,或者将整数限制在 0.0 到 1.0 的区间内? (两个范围都是封闭的,即包括端点。)
我正在使用明显的最小值-最大值检查:
int value = (value < 0? 0 : value > 255? 255 : value);
但是有没有办法让它更快——类似于 "modulo" 钳位 value & 255
?有没有办法用浮点数做类似的事情?
我正在寻找便携式解决方案,所以最好不要 CPU/GPU-specific 东西。
这是我用来将 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;
}
它仍然有一个分支,但它的一个方便之处在于,您可以通过对它们进行 OR 运算来一次测试多个整数中的任何一个是否超出范围,这使得在所有的常见情况下速度更快他们在范围内。例如:
/** 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);
在许多体系结构中,使用三元运算符 ?:
也可以产生无分支指令序列。硬件可能支持 select 类型的指令,这些指令本质上是三元运算符的硬件等价物,例如 NVIDIA GPU 上的 ICMP
。或者它提供了 x86 中的 CMOV
(条件移动),或 ARM 中的谓词,这两者都可用于为三元运算符实现无分支代码。与前一种情况一样,人们会想要检查反汇编的二进制代码,以绝对确保生成的代码没有分支。
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
}
对于那些使用 C#、Kotlin 或 Java 的人来说,这是我能做的最好的,虽然有点神秘,但它很好而且简洁:
(x & ~(x >> 31) | 255 - x >> 31) & 255
它只适用于有符号整数,因此对某些人来说可能是个阻碍。
对于夹紧双打,恐怕没有 language/platform 不可知的解决方案。
浮点数的问题,他们有从最快的操作(MSVC /fp:fast
、gcc -funsafe-math-optimizations
)到完全精确和安全(MSVC /fp:strict
、gcc [=13=)的选项]).在完全精确模式下,编译器不会尝试使用任何位黑客,即使他们可以。
操作 double
位的解决方案不可移植。可能有不同的字节顺序,也可能没有(有效的)方法来获取 double
位,毕竟 double
不一定是 IEEE 754 binary64。此外,直接操作不会在预期的情况下产生用于发送 NAN 信号的信号。
对于整数,编译器很可能无论如何都会正确处理,否则已经给出了很好的答案。
是否有任何无分支或类似的技巧可以将整数限制在 0 到 255 的区间内,或者将整数限制在 0.0 到 1.0 的区间内? (两个范围都是封闭的,即包括端点。)
我正在使用明显的最小值-最大值检查:
int value = (value < 0? 0 : value > 255? 255 : value);
但是有没有办法让它更快——类似于 "modulo" 钳位 value & 255
?有没有办法用浮点数做类似的事情?
我正在寻找便携式解决方案,所以最好不要 CPU/GPU-specific 东西。
这是我用来将 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;
}
它仍然有一个分支,但它的一个方便之处在于,您可以通过对它们进行 OR 运算来一次测试多个整数中的任何一个是否超出范围,这使得在所有的常见情况下速度更快他们在范围内。例如:
/** 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);
在许多体系结构中,使用三元运算符 ?:
也可以产生无分支指令序列。硬件可能支持 select 类型的指令,这些指令本质上是三元运算符的硬件等价物,例如 NVIDIA GPU 上的 ICMP
。或者它提供了 x86 中的 CMOV
(条件移动),或 ARM 中的谓词,这两者都可用于为三元运算符实现无分支代码。与前一种情况一样,人们会想要检查反汇编的二进制代码,以绝对确保生成的代码没有分支。
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
}
对于那些使用 C#、Kotlin 或 Java 的人来说,这是我能做的最好的,虽然有点神秘,但它很好而且简洁:
(x & ~(x >> 31) | 255 - x >> 31) & 255
它只适用于有符号整数,因此对某些人来说可能是个阻碍。
对于夹紧双打,恐怕没有 language/platform 不可知的解决方案。
浮点数的问题,他们有从最快的操作(MSVC /fp:fast
、gcc -funsafe-math-optimizations
)到完全精确和安全(MSVC /fp:strict
、gcc [=13=)的选项]).在完全精确模式下,编译器不会尝试使用任何位黑客,即使他们可以。
操作 double
位的解决方案不可移植。可能有不同的字节顺序,也可能没有(有效的)方法来获取 double
位,毕竟 double
不一定是 IEEE 754 binary64。此外,直接操作不会在预期的情况下产生用于发送 NAN 信号的信号。
对于整数,编译器很可能无论如何都会正确处理,否则已经给出了很好的答案。