我想将signed short
变量的值限制在0到4095之间,之后我将最重要的8位作为我在其他地方使用的最终值。现在我正在以下面的基本方式做到这一点:
short color = /* some external source */;
/*
* I get the color value as a 16 bit signed integer from an
* external source I cannot trust. 16 bits are being used here
* for higher precision.
*/
if ( color < 0 ) {
color = 0;
}
else if ( color > 4095 ) {
color = 4095;
}
unsigned char color8bit = 0xFF & (color >> 4);
/*
* color8bit is my final value which I would actually use
* in my application.
*/
有没有办法只使用位操作,即不使用任何条件?它可能有助于加快速度,因为这个操作在代码中发生了数千次。
以下内容无效,因为它不会处理负值和溢出等边缘情况:
unsigned char color8bit = 0xFF & (( 0x0FFF & color ) >> 4 );
编辑: Adam Rosenfield's answer是采用正确方法但未正确实施的方法。 ouah's answer给出了正确的结果,但采用了我原本打算查找的不同方法。
这是我最终使用的:
const static short min = 0;
const static short max = 4095;
color = min ^ (( min ^ color ) & -( min < color ));
color = max ^ (( color ^ max ) & -( color < max ));
unsigned char color8bit = 0xFF & (( 0x0FFF & color ) >> 4 );
答案 0 :(得分:7)
是的,请参阅these bit-twiddling hacks:
short color = ...;
color = color ^ (color & -(color < 0)); // color = max(color, 0)
color = 4096 ^ ((color ^ 4096) & -(color < 4096)); // color = min(color, 4096)
unsigned char color8bit = 0xFF & (color >> 4);
这实际上是否真的变得更快,我不知道 - 你应该剖析。如今,大多数现代x86和x86-64芯片都支持“条件移动”指令(cmov),它有条件地存储取决于EFLAGS状态位的值,优化编译器通常会从color >= 0 ? color : 0
这样的三元表达式生成这些指令。这些可能会最快,但它们不会在较旧的x86芯片上运行。
答案 1 :(得分:5)
您可以执行以下操作:
BYTE data[0x10000] = { ..... };
BYTE byte_color = data[(unsiged short)short_color];
在你的日子里,64kb表并不是一件令人发指的事情,可能是可以接受的。与其他可能的方法相比,此代码变体中的汇编程序命令数量绝对最小。
答案 2 :(得分:2)
我假设short
是16位。
删除否定值:
int16_t mask=-(int16_t)((uint16_t)color>>15);//0xFFFF if +ve, 0 if -ve
short value=color&mask;//0 if -ve, colour if +ve
value
现在介于0和32767之间。
然后,您可以执行类似的操作来限制值:
mask=(uint16_t)(value-4096)>>15;//1 if <=4095, 0 if >4095
--mask;//0 if <=4095, 0xFFFF if >4095
mask&=0xFFF;//0 if <=4095, 4095 if >4095
value|=mask;//4095 if >4095, color if <4095
答案 3 :(得分:2)
short color = /* ... */
color = ((((!!(color >> 12)) * 0xFFF)) | (!(color >> 12) * color ))
& (!(color >> 15) * 0xFFF);
unsigned char color8bit = 0xFF & (color >> 4);
它假设两个补码表示。
这具有不使用任何相等或关系运算符的优点。在某些情况下,您需要不惜一切代价避免分支:在某些安全应用程序中,您不希望攻击者执行分支预测。没有分支(特别是在嵌入式处理器中),您可以使所有输入的函数在恒定时间内运行。
请注意:x * 0xFFF
可以进一步缩减为(x << 12) - x
。此外,(!(color >> 12) * color )
中的乘法也可以进一步优化,因为此处*
的左操作数为0
或1
。
修改强>
我添加了一些解释:上面的表达式与下面的表达式完全相同而不使用条件和关系运算符:
y = ((y > 4095 ? 4095 : 0) | (y > 4095 ? 0 : y))
& (y < 0 ? 0 : 4095);
<强> EDIT2:强>
正如@HotLicks在评论中正确指出的那样,!
仍然是一个概念分支。然而,它也可以使用按位运算符计算。例如,!!a
可以通过简单的方式完成:
b = (a >> 15 | a >> 14 | ... | a >> 1 | a) & 1
和!a
可以b ^ 1
完成。而且我确信有一个很好的黑客可以更有效地做到这一点。
答案 4 :(得分:1)
您还可以使用Intel's SSE intrinsics轻松地对其进行矢量化。一个128位寄存器将保存8个short
,并且有一些函数可以并行地对所有这些寄存器进行最小/最大/移位/屏蔽。在循环中,可以将min / max的常量预加载到寄存器中。 pshufb
指令(SSSE3的一部分)甚至会为您打包字节。
答案 5 :(得分:0)
即使它没有直接回答原始问题,我也会留下答案,因为最后我认为你会发现它更有用。
我假设您的颜色来自以12位运行的相机或图像扫描仪,然后是一些未确定的处理步骤,可能会创建超出0到4095范围的值。如果是这种情况,则几乎可以肯定地以线性方式得出值。问题是显示器是伽马校正的,因此从12位到8位的转换将需要非线性伽马函数而不是简单的右移。这将比您的问题试图优化的夹紧操作慢得多。如果您不使用伽玛功能,图像将显得太暗。
short color = /* some external source */;
unsigned char color8bit;
if (color <= 0)
color8bit = 0;
else if (color >= 4095)
color8bit = 255;
else
color8bit = (unsigned char)(255.99 * pow(color / 4095.0, 1/2.2));
此时您可能会考虑查找表as suggested by Kirill Kobelev。
答案 6 :(得分:0)
这有点类似于Tom Seddon的答案,但是使用一种稍微更清洁的方式来做上面的夹子。请注意,Seddon先生的回答和我的回答都避免了ouah的回答问题,即将签名值转移到右边是实现定义的行为,因此无法保证在所有的结构上都能正常工作。
#include <inttypes.h>
#include <iostream>
int16_t clamp(int16_t value)
{
// clampBelow is 0xffff for -ve, 0x0000 for +ve
int16_t const clampBelow = -static_cast<int16_t>(static_cast<uint16_t>(value) >> 15);
// value is now clamped below at zero
value &= ~clampBelow;
// subtract 4095 so we can do the same trick again
value -= 4095;
// clampAbove is 0xffff for -ve, 0x0000 for +ve,
// i.e. 0xffff for original value < 4095, 0x0000 for original >= 4096
int16_t const clampAbove = -static_cast<int16_t>(static_cast<uint16_t>(value) >> 15);
// adjusted value now clamped above at zero
value &= clampAbove;
// and restore to original value.
value += 4095;
return value;
}
void verify(int16_t value)
{
int16_t const clamped = clamp(value);
int16_t const check = (value < 0 ? 0 : value > 4095 ? 4095 : value);
if (clamped != check)
{
std::cout << "Verification falure for value: " << value << ", clamped: " << clamped << ", check: " << check << std::endl;
}
}
int main()
{
for (int16_t i = 0x4000; i != 0x3fff; i++)
{
verify(i);
}
return 0;
}
这是一个完整的测试程序(好的,所以它不会测试0x3fff - 起诉我。;)),你可以从中提取clamp()
例程以满足你的需要。
我也打破了每一行的一步&#34;&#34;为清楚起见。如果你的编译器有一个不错的优化器,你可以保持原样并依赖编译器产生最好的代码。如果您的编译器的优化器不是那么好,那么无论如何,它都可以减少行数,尽管代价是可读性稍差。
&#34;永远不要为了提高效率而牺牲清晰度&#34; - Bob Buckley,1980年英格兰考文垂U-Warwick的comp sci教授。
我得到的最佳建议。 ;)