4

我想将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 );

编辑: 亚当·罗森菲尔德的答案是采用正确方法但实施不正确的答案。ouah 的回答给出了正确的结果,但采用了与我最初打算找出的不同的方法。

这就是我最终使用的:

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 );
4

7 回答 7

7

是的,看看这些小技巧

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 芯片上运行。

于 2012-09-06T21:51:29.683 回答
5

您可以执行以下操作:

BYTE data[0x10000] = { ..... };

BYTE byte_color = data[(unsiged short)short_color];

在你的日子里,64kb 表并不是什么离谱的事情,可能是可以接受的。与其他可能的方法相比,此代码变体中的汇编器命令数量绝对是最少的。

于 2012-09-06T21:50:14.060 回答
2

我假设 ashort是 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
于 2012-09-06T22:05:33.913 回答
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 )此外,也可以进一步优化in 的乘法,因为*这里的左操作数是0or 1

编辑:

我添加一点解释:上面的表达式与下面的表达式相同,没有使用条件和关系运算符:

y =   ((y > 4095 ? 4095 : 0) | (y > 4095 ? 0 : y))
    & (y < 0 ? 0 : 4095);

编辑2:

正如@HotLicks 在他的评论中正确指出的那样,这!仍然是一个概念分支。尽管如此,它也可以使用按位运算符计算。例如!!a可以用简单的方法来完成:

b = (a >> 15 | a >> 14 | ... | a >> 1 | a) & 1

并且!a可以作为b ^ 1. 而且我确信有一个很好的技巧可以更有效地做到这一点。

于 2012-09-06T23:03:52.143 回答
1

您还可以使用英特尔的 SSE 内在函数轻松地对其进行矢量化。一个 128 位寄存器可以容纳 8 个您的寄存器,short并且有一些函数可以并行地最小/最大/移位/屏蔽所有这些。在循环中,最小值/最大值的常量可以预加载到寄存器中。该pshufb指令(SSSE3 的一部分)甚至会为您打包字节。

于 2012-09-06T23:44:03.880 回答
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));

此时,您可能会考虑Kirill Kobelev 建议的查找表。

于 2012-09-07T00:01:47.507 回答
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()例程以满足您的需要。

为了清楚起见,我还将钳位打破为“每行一步”。如果你的编译器有一个不错的优化器,你可以保持原样并依靠编译器来产生最好的代码。如果您的编译器的优化器不是那么好,那么无论如何都可以减少行数,尽管以牺牲一点可读性为代价。

“永远不要为了效率而牺牲清晰度”—— Bob Buckley,计算机科学教授,U-Warwick,考文垂,英格兰,1980 年。

我得到的最好的建议。;)

于 2017-06-27T03:02:33.720 回答