2016-11-09 117 views
0

我主演在原来的JPEG标准(ITU 81),特别是图中图F.12:移带负号的值是未定义的

参考:以V延伸的解码值的符号位在SLL术语表示:shift left logical operation(见PDF第15页)

Figure F.12: extend sign bit.

现在著名的libjpeg执行决定实施它this way(相当直接转录):

/* 
* Figure F.12: extend sign bit. 
* On some machines, a shift and add will be faster than a table lookup. 
*/ 

#ifdef AVOID_TABLES 

#define HUFF_EXTEND(x,s) ((x) < (1<<((s)-1)) ? (x) + (((-1)<<(s)) + 1) : (x)) 

#else 

#define HUFF_EXTEND(x,s) ((x) < extend_test[s] ? (x) + extend_offset[s] : (x)) 

static const int extend_test[16] = /* entry n is 2**(n-1) */ 
    { 0, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, 
    0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 }; 

static const int extend_offset[16] = /* entry n is (-1 << n) + 1 */ 
    { 0, ((-1)<<1) + 1, ((-1)<<2) + 1, ((-1)<<3) + 1, ((-1)<<4) + 1, 
    ((-1)<<5) + 1, ((-1)<<6) + 1, ((-1)<<7) + 1, ((-1)<<8) + 1, 
    ((-1)<<9) + 1, ((-1)<<10) + 1, ((-1)<<11) + 1, ((-1)<<12) + 1, 
    ((-1)<<13) + 1, ((-1)<<14) + 1, ((-1)<<15) + 1 }; 

#endif /* AVOID_TABLES */ 

很明显移位负符号值UB,所以我想知道什么JPEG标准的原作者居然在这里的意思。 JPEG标准是否限于二进制补码表示?

+0

据我所知,移位一个负符号值是实现定义的(只要移位量为正数且在'sizeof(operator)* CHAR_BIT'内) – bolov

+3

@bolov,* right * - 移动一个负值是实现定义的。 *另一方面,左移*移动签名类型的值的方法是***未定义的,除非原始值是非负数,并且数学结果可以在结果类型中表示。特别是,OP十分正确,左移-1产生UB。 –

+0

@JohnBollinger谢谢。总有一些事情需要重新学习C或C++。 – bolov

回答

3

JPEG标准是否限于二进制补码表示?

使用图表使用SLL而不是*2,流程图依赖于2的补码实现。 C,OTOH不限于2的补码,也不限于以某种“2的补码”方式使用移位。更好的编码没有这个假设。使用无符号类型是很好的第一步。

// ((-1)<<15) + 1 
((-1u)<<15) + 1 

需要查看HUFF_EXTEND()的应用程序的更深的答案。

2

在C语言中转移负值只是未定义的行为。在汇编程序级别,这种转换非常好。逻辑/算术左移指令将MSB移入进位位,就是这样。 CPU不会以不确定的方式停止并着火。

也就是说,您可以通过将您签名的号码转换为无符号来避免UB。那么你只会有实现定义的行为。例如,(int)(-1u<<1)确实不会调用UB,即使这样的代码看起来相当可疑。

安全的做法是对无符号数(uint32_t)执行所有计算并仅在实际需要时将其转换为带符号。切勿对签名类型执行任何形式的按位操作。

我完全忽略了与不使用二进制补码的非常奇特/虚构系统的兼容性。关注与真实世界主流电脑的兼容性。

+0

不,只移动_负性位移(或大于或等于左操作数大小位移)是未定义的behaviour_。由于左移相当于一个*乘以两个乘法*,对于'signed',C相同也是正确的移位(乘以除数)。实际发生的情况是'unsigned int'移动**不同于** signed int'值移动。为了保持签名并继续作为两次操作的分界线,右移是impl。因为_sign扩展除以两个操作_在'signed's。 'ASR' /'LSR'机器指令与'signed' /'unsigned's一起使用, –

0

很明显,变速负数量具有由两个作为乘法/除法升高到n次幂(为a <<n/a>> n其中asigned)这是永远不会为负左操作数未定义的行为相同的效果,并对于C/C++来说已经很好并精确定义了。

那么,尽管接受的答案已被选中,并且关于运算符<<>>的讨论的数量已经生成,但我会尝试澄清什么是未定义的,哪些不是。

  • <<操作者上signedunsigned左操作数的数量同样的行为,作为一个整数的净效应是在数值加倍,并且该过程是用于signedunsigned数量是相同的,只是插入的一个0数字的右侧部分,将所有数字位移到所指示的数字,作为正确的操作符。

  • >>操作行为不同的signedunsigned左操作数,作为净效应,同样,像整数除法由两个提高到右边的操作数,这意味着,的二进制补码表示延长最显着位(0左边变成001左边变成11)实现了除以2的除法的净效果,右边的操作数的权力提高(-24 >> 3变为-3,除以2^3,和24 >> 3变成3)当使用unsigned整数时,此行为改变(0变为001变为01)使得再次像二分之一的移位提升到正确的运算符的能力。

由于标准说,行为是未定义如果您尝试左/右移位位(右操作者必须> 0)负量或量大于或小于左操作物的大小相等位。但是这只影响右操作数,而不影响左操作数。

试图获得有关JPEG解码的解释,试图认为JPEG解码使用离散实数近似值编码为有符号数量的COSIN变换(它定义为实矢量),或者它使用带符号整数来评估低精度采样,因此他们从不处理无符号数量(库设计人员假定对整数进行操作比对浮点数进行操作要快)。

  • -24是二进制1111111...1111101000,并且通过3右移位使得它11111...11111101,或-3。如果我们再次移动它,它将变为111...1110-2-1.5四舍五入至负无穷),并且再次变为1111...1111-1
  • 2400000000....00011000并右移它变成000000...0000011这是3。如果我们再次移动它,它将变为000...000111.5四舍五入为负无穷),并且我们再次获得000...000000.5四舍五入至负无穷大)。

(请注意这种方法制成的消极转变截断高达负无穷大,使得-1 >> 1成为-1,为-0.5截断到-1而不是0。)