目标:对任意数字执行“算术”向右移位。
这意味着符号位必须被扩展 – 如果数字的最高有效位被设置,二进制数将从1开始填充,而不是0。
因此,在算术移位之后的数字“-1”必须保持相同的数(所有位= 1),但是“逻辑移位”(其总是用零填充数)必须给出最大正整数(最大正有符号整数,要正确)
我测试它只在32位系统(Windows);此外,我需要它明确地工作与32位整数。
看起来在Delphi中有一个内部错误,当源数字存储在一个变量中时为“shr”。
我的示例代码:
program bug; {$APPTYPE CONSOLE} var I:Integer; C:Cardinal; begin I := -1; // we’ll need that later C := $FFFFFFFF;
(这只是开始)。接下来,让我们尝试一些“shr”:
Writeln('0) ',-1 shr 1 ); Writeln('1) ',$FFFFFFFF shr 1 );
“-1”是等价于“$ FFFFFFFF”的签名。似乎“shr”行为(算术或逻辑)是基于源数字是有符号还是不是(整数或基数)的事实。
输出为:
0) -1 1) 2147483647
非常正确。然后我需要尝试手动将这些数字转换为整数或红雀:
Writeln('2) ',Integer(-1) shr 1 ); Writeln('3) ',Integer($FFFFFFFF) shr 1 ); Writeln('4) ',Cardinal(-1) shr 1 ); Writeln('5) ',Cardinal($FFFFFFFF) shr 1 );
结果:
2) -1 3) -1 4) 2147483647 5) 2147483647
仍然正确。所以,我认为如果我需要算术移位,我可以把任何东西转换为“整数”;或者当我想要逻辑移位时转换为“基数”。可是等等!带变量的示例(如上所述):
Writeln('6) ',I shr 1 ); Writeln('7) ',C shr 1 );
突然:
6) 2147483647 7) 2147483647
不正确。我的“I”是一个有符号整数,我期待算术移位!所以,也许铸造可以帮助?
Writeln('8) ',Integer(I) shr 1 ); Writeln('9) ',Cardinal(I) shr 1 ); Writeln('A) ',Integer(C) shr 1 ); Writeln('B) ',Cardinal(C) shr 1 );
不,还是一样的…
8) 2147483647 9) 2147483647 A) 2147483647 B) 2147483647
如果我尝试创建一个函数“a shr b”并使用它,情况会更糟:
// Simple shift right with signed integers function shrI(a,b:Integer):Integer; begin Result := a shr b; end; // Simple shift right with unsigned integers function shrC(a,b:Cardinal):Cardinal; begin Result := a shr b; end;
现在:
Writeln('C) ',shrI(-1,1) ); Writeln('D) ',shrC($FFFFFFFF,1) );
– 它停止工作,即使使用常量表达式:(这是有道理的,因为数字再次存储在函数内的变量)
C) 2147483647 D) 2147483647
因为我需要进行正确的算术移位,我写了这些公式来做这个(将“a”右移“b”位)。首先是逻辑转移:
(a shr b) and ((1 shl (32-b))-1)
我只需要按位,结果与“32 – b”(从右)清除“b”左位,如果“shr”失败,我做了算术移位(没有例子说明这一点,但只是为了当然)。然后算术移位:
(a shr b) or (( 0-((a shr 31) and 1)) shl (32-b))
我需要按位或结果从左边的“b”,但只有当最高有效位被设置;首先我使用“(a shr 31)和1”符号位,然后否定这个数字以获得“-1”(或$ FFFFFFFF – 所有位= 1),如果源为负,否则为0否则“0-x”而不是“-x”,因为在我的C端口在某些情况下bcc32 C编译器报告绑定以取消无符号整数的警告);最后我把它移到“32 – b”左边,所以我得到了我想要的,即使“shr”失败,并给出零。我做了两个版本的每个函数来处理整数和红衣主教(也可以共享名称和“超载”他们为我,但在这里我不会这样做,以保持示例清晰):
// Logical shift right with signed integers function srlI(a,b:Integer):Integer; begin Result := (a shr b) and ((1 shl (32-b))-1); end; // Arithmetic shift right with signed integers function sraI(a,b:Integer):Integer; begin Result := (a shr b) or (( 0-((a shr 31) and 1)) shl (32-b)); end; // Logical shift right with unsigned integers function srlC(a,b:Cardinal):Cardinal; begin Result := (a shr b) and ((1 shl (32-b))-1); end; // Arithmetic shift right with unsigned integers function sraC(a,b:Cardinal):Cardinal; begin Result := (a shr b) or (( 0-((a shr 31) and 1)) shl (32-b)); end;
测试:
Writeln('E) ',sraI(-1,1) ); Writeln('F) ',srlI(-1,1) ); Writeln('G) ',sraC($FFFFFFFF,1) ); Writeln('H) ',srlC($FFFFFFFF,1) );
并得到完美的结果:
E) -1 F) 2147483647 G) 4294967295 H) 2147483647
(G-case仍然是正确的,因为“4294967295”是“-1”的无符号版本)
使用变量的最终检查:
Writeln('K) ',sraI(I,1) ); Writeln('L) ',srlI(I,1) ); Writeln('M) ',sraC(C,1) ); Writeln('N) ',srlC(C,1) );
完善:
K) -1 L) 2147483647 M) 4294967295 N) 2147483647
对于这个bug,我也试图改变第二个数字(移位量)到一个变量和/或尝试不同的铸造它 – 相同的bug存在,看起来像它不与第二个参数相关。并试图把结果(整数或基数)之前输出没有改善任何东西。
为了确保我不只是一个有错误的人,我试图运行我的整个例子在http://codeforces.com/(有一个注册用户可以编译和执行一段代码在不同的语言和服务器端的编译器)看到输出。
“Delphi 7”编译器给了我什么我有 – bug存在。替代选项,“Free Pascal 2”显示更多错误的输出:
0) 9223372036854775807 1) 2147483647 2) 9223372036854775807 3) 9223372036854775807 4) 2147483647 5) 2147483647 6) 2147483647 7) 2147483647 8) 2147483647 9) 2147483647 A) 2147483647 B) 2147483647 C) 2147483647 D) 2147483647 E) -1 F) 2147483647 G) 4294967295 H) 2147483647 K) -1 L) 2147483647 M) 4294967295 N) 2147483647
在情况0-2-3(有“-1”,“Integer(-1)”和“Integer($ FFFFFFFF)”不记得)中奇怪“9223372036854775807”。
这里是我在Delphi的整个例子:
program bug; {$APPTYPE CONSOLE} // Simple shift right with signed integers function shrI(a,b:Cardinal):Cardinal; begin Result := a shr b; end; // Logical shift right with signed integers function srlI(a,b:Cardinal):Cardinal; begin Result := (a shr b) or (( 0-((a shr 31) and 1)) shl (32-b)); end; var I:Integer; C:Cardinal; begin I := -1; C := $FFFFFFFF; Writeln('0) ',$FFFFFFFF shr 1 ); // 0) -1 - correct // 1) 2147483647 - correct Writeln('2) ',Integer($FFFFFFFF) shr 1 ); // 2) -1 - correct // 3) -1 - correct Writeln('4) ',Cardinal($FFFFFFFF) shr 1 ); // 4) 2147483647 - correct // 5) 2147483647 - correct Writeln('6) ',C shr 1 ); // 6) 2147483647 - INCORRECT! // 7) 2147483647 - correct Writeln('8) ',Cardinal(I) shr 1 ); // 8) 2147483647 - INCORRECT! // 9) 2147483647 - correct Writeln('A) ',Cardinal(C) shr 1 ); // A) 2147483647 - INCORRECT! // B) 2147483647 - correct Writeln('C) ',1) ); // C) 2147483647 - INCORRECT! // D) 2147483647 - correct Writeln('E) ',1) ); // E) -1 - correct // F) 2147483647 - correct Writeln('G) ',1) ); // G) 4294967295 - correct // H) 2147483647 - correct Writeln('K) ',1) ); // K) -1 - correct // L) 2147483647 - correct Writeln('M) ',1) ); // M) 4294967295 - correct // N) 2147483647 - correct end.
然后我是curios,这个bug也存在于C吗?我写了一个端口到C和使用(Borland!)bcc32.exe来编译它。
结果:
0) -1 1) 2147483647 2) -1 3) -1 4) 2147483647 5) 2147483647 6) -1 7) 2147483647 8) -1 9) 2147483647 A) -1 B) 2147483647 C) -1 D) 2147483647 E) -1 F) 2147483647 G) 4294967295 H) 2147483647 K) -1 L) 2147483647 M) 4294967295 N) 2147483647
一切都好。这里是C版本,以防万一还想看看:
#include <iostream> using namespace std; // Simple shift right with signed integers int shrI(int a,int b){ return a >> b; } // Simple shift right with unsigned integers unsigned int shrC(unsigned int a,unsigned int b){ return a >> b; } // Logical shift right with signed integers int srlI(int a,int b){ return (a >> b) & ((1 << (32-b))-1); } // Arithmetic shift right with signed integers int sraI(int a,int b){ return (a >> b) | (( 0-((a >> 31) & 1)) << (32-b)); } // Logical shift right with unsigned integers unsigned int srlC(unsigned int a,unsigned int b){ return (a >> b) & ((1 << (32-b))-1); } // Arithmetic shift right with unsigned integers unsigned int sraC(unsigned int a,unsigned int b){ return (a >> b) | (( 0-((a >> 31) & 1)) << (32-b)); } int I; unsigned int C; int main(){ I = -1; C = 0xFFFFFFFF; cout<<"0) "<<( -1 >> 1 )<<endl; cout<<"1) "<<( 0xFFFFFFFF >> 1 )<<endl; // 0) -1 - correct // 1) 2147483647 - correct cout<<"2) "<<( ((int)(-1)) >> 1 )<<endl; cout<<"3) "<<( ((int)(0xFFFFFFFF)) >> 1 )<<endl; // 2) -1 - correct // 3) -1 - correct cout<<"4) "<<( ((unsigned int)(-1)) >> 1 )<<endl; cout<<"5) "<<( ((unsigned int)(0xFFFFFFFF)) >> 1 )<<endl; // 4) 2147483647 - correct // 5) 2147483647 - correct cout<<"6) "<<( I >> 1 )<<endl; cout<<"7) "<<( C >> 1 )<<endl; // 6) -1 - correct // 7) 2147483647 - correct cout<<"8) "<<( ((int)(I)) >> 1 )<<endl; cout<<"9) "<<( ((unsigned int)(I)) >> 1 )<<endl; // 8) -1 - correct // 9) 2147483647 - correct cout<<"A) "<<( ((int)(C)) >> 1 )<<endl; cout<<"B) "<<( ((unsigned int)(C)) >> 1 )<<endl; // A) -1 - correct // B) 2147483647 - correct cout<<"C) "<<( shrI(-1,1) )<<endl; cout<<"D) "<<( shrC(0xFFFFFFFF,1) )<<endl; // C) -1 - correct // D) 2147483647 - correct cout<<"E) "<<( sraI(-1,1) )<<endl; cout<<"F) "<<( srlI(-1,1) )<<endl; // E) -1 - correct // F) 2147483647 - correct cout<<"G) "<<( sraC(0xFFFFFFFF,1) )<<endl; cout<<"H) "<<( srlC(0xFFFFFFFF,1) )<<endl; // G) 4294967295 - correct // H) 2147483647 - correct cout<<"K) "<<( sraI(I,1) )<<endl; cout<<"L) "<<( srlI(I,1) )<<endl; // K) -1 - correct // L) 2147483647 - correct cout<<"M) "<<( sraC(C,1) )<<endl; cout<<"N) "<<( srlC(C,1) )<<endl; // M) 4294967295 - correct // N) 2147483647 - correct }
在发布之前,我试图搜索这个问题,并没有发现任何提到这个bug。我也看了这里:What is the behaviour of shl and shr for non register sized operands?和这里:Arithmetic Shift Right rather than Logical Shift Right – 但有其他问题讨论(编译器内部转换任何类型到32位数字之前做实际的移位;或移位超过31位),但不是我的错误。
但等等,这里是我的问题:http://galfar.vevb.net/wp/2009/shift-right-delphi-vs-c/!
有一句话:他们说 –
In Delphi the SHR is always a SHR operation: it never takes into account the sign.
但是我的例子表明,Delphi确实考虑到符号,但只有当源号是一个常量表达式,而不是一个变量。因此,“-10 shr 2”等于“-3”,但是当“x:= – 10”时,“x shr 2”等于“1073741821”。
所以我认为这是一个bug,而不是一个“行为”,“shr”总是逻辑。你看,不总是。
尝试启用/禁用任何编译器选项,如范围检查或优化没有更改任何内容。
此外,在这里我发布了如何绕过这个问题的例子,并有正确的算术移位权。我的主要问题是:我是对吗?
看起来在Delphi中左移总是好的(它从来不使用原始符号位,而不是“未定义”:对于有符号整数,它在转换之前表现为转换为基数,并将结果转换回整数 – 数字可能突然变为负数课程)。但现在我不知道,在Delphi中有没有其他类似的错误?这是我在Delphi 7中发现的第一个真正重要的错误。我喜欢Delphi超过C,因为我总是确保我的代码是每次做我想要的,没有调试测试每一个新的不寻常的代码,我即将写入(IMHO)。
P.S。这里有一些有用的链接,StackOverflow系统建议我,当我在打印这个问题之前输入我的标题。再次,有趣的信息,但不是关于这个bug:
Arithmetic bit-shift on a signed integer
Signed right shift = strange result?
Bitwise shift operators on signed types
Should you always use ‘int’ for numbers in C,even if they are non-negative?
Bitwise operation on signed integer
Verifying that C/C++++ signed right shift is arithmetic for a particular compiler?
Emulating variable bit-shift using only constant shifts?
P.P.S.非常感谢Stack Exchange Team在发布这篇文章方面的帮助。伙计们,你摇滚!
解决方法
If x is a negative integer,the shl and shr operations are made clear
in the following example:06000
因此,shr和shl总是逻辑移位而不是算术移位。
缺陷实际上是处理负的真实常数:
Writeln('0) ',-1 shr 1 );
这里,-1是有符号值。它实际上有Shortint类型,一个有符号的8位整数。但移位运算符对32位值进行运算,因此符号扩展为32位值。这意味着这段摘录应该产生具有相同输出的两行:
var i: Integer; .... i := -1; Writeln(-1 shr 1); Writeln( i shr 1);
并且输出应该是:
2147483647 2147483647
在现代版本的Delphi,肯定从版本2010和更高版本,但可能甚至更早的版本,这是case。
但是根据你的问题,在Delphi 7中,-1 shr 1评估为-1,这是错误的,因为shr是逻辑移位。
我们可以猜测缺陷的来源。编译器评估-1 shr 1,因为它是一个常量值,编译器简单地使用算术移位而不是逻辑移位不正确。
顺便说一句,文档包含另一个错误。它说:
The operations x shl y and x shr y shift the value of x to the left or right by y bits,which (if x is an unsigned integer) is equivalent to multiplying or dividing x by 2^y; the result is of the same type as x.
最后一部分不是真的。如果x是8,16或32位类型,则表达式x shl y是32位类型,否则是64位类型。
因为你的实际目标是实现算术移位,所以这些都不重要。你不能使用shl或shr。你必须自己实现算术移位。我建议你这样使用内联汇编程序,因为我怀疑,最终可能更容易阅读和验证。