算术按位右移“a shr b”,带有存储在变量中的有符号整数 – 错误的结果!内部Delphi的错误?

前端之家收集整理的这篇文章主要介绍了算术按位右移“a shr b”,带有存储在变量中的有符号整数 – 错误的结果!内部Delphi的错误?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我有一个问题(或更可能是一个错误报告)关于位移位行为在Delphi(测试在Borland德尔福7)。

目标:对任意数字执行“算术”向右移位。

这意味着符号位必须被扩展 – 如果数字的最高有效位被设置,二进制数将从1开始填充,而不是0。

因此,在算术移位之后的数字“-1”必须保持相同的数(所有位= 1),但是“逻辑移位”(其总是用零填充数)必须给出最大正整数(最大正有符号整数,要正确)

我测试它只在32位系统(Windows);此外,我需要它明确地工作与32位整数。

看起来在Delphi中有一个内部错误,当源数字存储在一个变量中时为“shr”。

我的示例代码

  1. program bug;
  2.  
  3. {$APPTYPE CONSOLE}
  4.  
  5. var
  6. I:Integer;
  7. C:Cardinal;
  8.  
  9. begin
  10. I := -1; // we’ll need that later
  11. C := $FFFFFFFF;

(这只是开始)。接下来,让我们尝试一些“shr”:

  1. Writeln('0) ',-1 shr 1 );
  2. Writeln('1) ',$FFFFFFFF shr 1 );

“-1”是等价于“$ FFFFFFFF”的签名。似乎“shr”行为(算术或逻辑)是基于源数字是有符号还是不是(整数或基数)的事实。

输出为:

  1. 0) -1
  2. 1) 2147483647

非常正确。然后我需要尝试手动将这些数字转换为整数或红雀:

  1. Writeln('2) ',Integer(-1) shr 1 );
  2. Writeln('3) ',Integer($FFFFFFFF) shr 1 );
  3. Writeln('4) ',Cardinal(-1) shr 1 );
  4. Writeln('5) ',Cardinal($FFFFFFFF) shr 1 );

结果:

  1. 2) -1
  2. 3) -1
  3. 4) 2147483647
  4. 5) 2147483647

仍然正确。所以,我认为如果我需要算术移位,我可以把任何东西转换为“整数”;或者当我想要逻辑移位时转换为“基数”。可是等等!带变量的示例(如上所述):

  1. Writeln('6) ',I shr 1 );
  2. Writeln('7) ',C shr 1 );

突然:

  1. 6) 2147483647
  2. 7) 2147483647

不正确。我的“I”是一个有符号整数,我期待算术移位!所以,也许铸造可以帮助?

  1. Writeln('8) ',Integer(I) shr 1 );
  2. Writeln('9) ',Cardinal(I) shr 1 );
  3. Writeln('A) ',Integer(C) shr 1 );
  4. Writeln('B) ',Cardinal(C) shr 1 );

不,还是一样的…

  1. 8) 2147483647
  2. 9) 2147483647
  3. A) 2147483647
  4. B) 2147483647

如果我尝试创建一个函数“a shr b”并使用它,情况会更糟:

  1. // Simple shift right with signed integers
  2. function shrI(a,b:Integer):Integer;
  3. begin
  4. Result := a shr b;
  5. end;
  6.  
  7. // Simple shift right with unsigned integers
  8. function shrC(a,b:Cardinal):Cardinal;
  9. begin
  10. Result := a shr b;
  11. end;

现在:

  1. Writeln('C) ',shrI(-1,1) );
  2. Writeln('D) ',shrC($FFFFFFFF,1) );

– 它停止工作,即使使用常量表达式:(这是有道理的,因为数字再次存储在函数内的变量)

  1. C) 2147483647
  2. D) 2147483647

因为我需要进行正确的算术移位,我写了这些公式来做这个(将“a”右移“b”位)。首先是逻辑转移:

  1. (a shr b) and ((1 shl (32-b))-1)

我只需要按位,结果与“32 – b”(从右)清除“b”左位,如果“shr”失败,我做了算术移位(没有例子说明这一点,但只是为了当然)。然后算术移位:

  1. (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”失败,并给出零。我做了两个版本的每个函数来处理整数和红衣主教(也可以共享名称和“超载”他们为我,但在这里我不会这样做,以保持示例清晰):

  1. // Logical shift right with signed integers
  2. function srlI(a,b:Integer):Integer;
  3. begin
  4. Result := (a shr b) and ((1 shl (32-b))-1);
  5. end;
  6.  
  7. // Arithmetic shift right with signed integers
  8. function sraI(a,b:Integer):Integer;
  9. begin
  10. Result := (a shr b) or (( 0-((a shr 31) and 1)) shl (32-b));
  11. end;
  12.  
  13. // Logical shift right with unsigned integers
  14. function srlC(a,b:Cardinal):Cardinal;
  15. begin
  16. Result := (a shr b) and ((1 shl (32-b))-1);
  17. end;
  18.  
  19. // Arithmetic shift right with unsigned integers
  20. function sraC(a,b:Cardinal):Cardinal;
  21. begin
  22. Result := (a shr b) or (( 0-((a shr 31) and 1)) shl (32-b));
  23. end;

测试:

  1. Writeln('E) ',sraI(-1,1) );
  2. Writeln('F) ',srlI(-1,1) );
  3. Writeln('G) ',sraC($FFFFFFFF,1) );
  4. Writeln('H) ',srlC($FFFFFFFF,1) );

并得到完美的结果:

  1. E) -1
  2. F) 2147483647
  3. G) 4294967295
  4. H) 2147483647

(G-case仍然是正确的,因为“4294967295”是“-1”的无符号版本)

使用变量的最终检查:

  1. Writeln('K) ',sraI(I,1) );
  2. Writeln('L) ',srlI(I,1) );
  3. Writeln('M) ',sraC(C,1) );
  4. Writeln('N) ',srlC(C,1) );

完善:

  1. K) -1
  2. L) 2147483647
  3. M) 4294967295
  4. N) 2147483647

对于这个bug,我也试图改变第二个数字(移位量)到一个变量和/或尝试不同的铸造它 – 相同的bug存在,看起来像它不与第二个参数相关。并试图把结果(整数或基数)之前输出没有改善任何东西。

为了确保我不只是一个有错误的人,我试图运行我的整个例子在http://codeforces.com/(有一个注册用户可以编译和执行一段代码在不同的语言和服务器端的编译器)看到输出

“Delphi 7”编译器给了我什么我有 – bug存在。替代选项,“Free Pascal 2”显示更多错误输出

  1. 0) 9223372036854775807
  2. 1) 2147483647
  3. 2) 9223372036854775807
  4. 3) 9223372036854775807
  5. 4) 2147483647
  6. 5) 2147483647
  7. 6) 2147483647
  8. 7) 2147483647
  9. 8) 2147483647
  10. 9) 2147483647
  11. A) 2147483647
  12. B) 2147483647
  13. C) 2147483647
  14. D) 2147483647
  15. E) -1
  16. F) 2147483647
  17. G) 4294967295
  18. H) 2147483647
  19. K) -1
  20. L) 2147483647
  21. M) 4294967295
  22. N) 2147483647

在情况0-2-3(有“-1”,“Integer(-1)”和“Integer($ FFFFFFFF)”不记得)中奇怪“9223372036854775807”。

这里是我在Delphi的整个例子:

  1. program bug;
  2.  
  3. {$APPTYPE CONSOLE}
  4.  
  5. // Simple shift right with signed integers
  6. function shrI(a,b:Cardinal):Cardinal;
  7. begin
  8. Result := a shr b;
  9. end;
  10.  
  11. // Logical shift right with signed integers
  12. function srlI(a,b:Cardinal):Cardinal;
  13. begin
  14. Result := (a shr b) or (( 0-((a shr 31) and 1)) shl (32-b));
  15. end;
  16.  
  17. var
  18. I:Integer;
  19. C:Cardinal;
  20.  
  21. begin
  22. I := -1;
  23. C := $FFFFFFFF;
  24.  
  25. Writeln('0) ',$FFFFFFFF shr 1 );
  26. // 0) -1 - correct
  27. // 1) 2147483647 - correct
  28.  
  29. Writeln('2) ',Integer($FFFFFFFF) shr 1 );
  30. // 2) -1 - correct
  31. // 3) -1 - correct
  32.  
  33. Writeln('4) ',Cardinal($FFFFFFFF) shr 1 );
  34. // 4) 2147483647 - correct
  35. // 5) 2147483647 - correct
  36.  
  37. Writeln('6) ',C shr 1 );
  38. // 6) 2147483647 - INCORRECT!
  39. // 7) 2147483647 - correct
  40.  
  41. Writeln('8) ',Cardinal(I) shr 1 );
  42. // 8) 2147483647 - INCORRECT!
  43. // 9) 2147483647 - correct
  44.  
  45. Writeln('A) ',Cardinal(C) shr 1 );
  46. // A) 2147483647 - INCORRECT!
  47. // B) 2147483647 - correct
  48.  
  49. Writeln('C) ',1) );
  50. // C) 2147483647 - INCORRECT!
  51. // D) 2147483647 - correct
  52.  
  53. Writeln('E) ',1) );
  54. // E) -1 - correct
  55. // F) 2147483647 - correct
  56.  
  57. Writeln('G) ',1) );
  58. // G) 4294967295 - correct
  59. // H) 2147483647 - correct
  60.  
  61. Writeln('K) ',1) );
  62. // K) -1 - correct
  63. // L) 2147483647 - correct
  64.  
  65. Writeln('M) ',1) );
  66. // M) 4294967295 - correct
  67. // N) 2147483647 - correct
  68.  
  69. end.

然后我是curios,这个bug也存在于C吗?我写了一个端口到C和使用(Borland!)bcc32.exe来编译它。

结果:

  1. 0) -1
  2. 1) 2147483647
  3. 2) -1
  4. 3) -1
  5. 4) 2147483647
  6. 5) 2147483647
  7. 6) -1
  8. 7) 2147483647
  9. 8) -1
  10. 9) 2147483647
  11. A) -1
  12. B) 2147483647
  13. C) -1
  14. D) 2147483647
  15. E) -1
  16. F) 2147483647
  17. G) 4294967295
  18. H) 2147483647
  19. K) -1
  20. L) 2147483647
  21. M) 4294967295
  22. N) 2147483647

一切都好。这里是C版本,以防万一还想看看:

  1. #include <iostream>
  2. using namespace std;
  3.  
  4. // Simple shift right with signed integers
  5. int shrI(int a,int b){
  6. return a >> b;
  7. }
  8.  
  9. // Simple shift right with unsigned integers
  10. unsigned int shrC(unsigned int a,unsigned int b){
  11. return a >> b;
  12. }
  13.  
  14. // Logical shift right with signed integers
  15. int srlI(int a,int b){
  16. return (a >> b) & ((1 << (32-b))-1);
  17. }
  18.  
  19. // Arithmetic shift right with signed integers
  20. int sraI(int a,int b){
  21. return (a >> b) | (( 0-((a >> 31) & 1)) << (32-b));
  22. }
  23.  
  24. // Logical shift right with unsigned integers
  25. unsigned int srlC(unsigned int a,unsigned int b){
  26. return (a >> b) & ((1 << (32-b))-1);
  27. }
  28.  
  29. // Arithmetic shift right with unsigned integers
  30. unsigned int sraC(unsigned int a,unsigned int b){
  31. return (a >> b) | (( 0-((a >> 31) & 1)) << (32-b));
  32. }
  33.  
  34. int I;
  35. unsigned int C;
  36.  
  37. int main(){
  38. I = -1;
  39. C = 0xFFFFFFFF;
  40.  
  41. cout<<"0) "<<( -1 >> 1 )<<endl;
  42. cout<<"1) "<<( 0xFFFFFFFF >> 1 )<<endl;
  43. // 0) -1 - correct
  44. // 1) 2147483647 - correct
  45.  
  46. cout<<"2) "<<( ((int)(-1)) >> 1 )<<endl;
  47. cout<<"3) "<<( ((int)(0xFFFFFFFF)) >> 1 )<<endl;
  48. // 2) -1 - correct
  49. // 3) -1 - correct
  50.  
  51. cout<<"4) "<<( ((unsigned int)(-1)) >> 1 )<<endl;
  52. cout<<"5) "<<( ((unsigned int)(0xFFFFFFFF)) >> 1 )<<endl;
  53. // 4) 2147483647 - correct
  54. // 5) 2147483647 - correct
  55.  
  56. cout<<"6) "<<( I >> 1 )<<endl;
  57. cout<<"7) "<<( C >> 1 )<<endl;
  58. // 6) -1 - correct
  59. // 7) 2147483647 - correct
  60.  
  61. cout<<"8) "<<( ((int)(I)) >> 1 )<<endl;
  62. cout<<"9) "<<( ((unsigned int)(I)) >> 1 )<<endl;
  63. // 8) -1 - correct
  64. // 9) 2147483647 - correct
  65.  
  66. cout<<"A) "<<( ((int)(C)) >> 1 )<<endl;
  67. cout<<"B) "<<( ((unsigned int)(C)) >> 1 )<<endl;
  68. // A) -1 - correct
  69. // B) 2147483647 - correct
  70.  
  71. cout<<"C) "<<( shrI(-1,1) )<<endl;
  72. cout<<"D) "<<( shrC(0xFFFFFFFF,1) )<<endl;
  73. // C) -1 - correct
  74. // D) 2147483647 - correct
  75.  
  76. cout<<"E) "<<( sraI(-1,1) )<<endl;
  77. cout<<"F) "<<( srlI(-1,1) )<<endl;
  78. // E) -1 - correct
  79. // F) 2147483647 - correct
  80.  
  81. cout<<"G) "<<( sraC(0xFFFFFFFF,1) )<<endl;
  82. cout<<"H) "<<( srlC(0xFFFFFFFF,1) )<<endl;
  83. // G) 4294967295 - correct
  84. // H) 2147483647 - correct
  85.  
  86. cout<<"K) "<<( sraI(I,1) )<<endl;
  87. cout<<"L) "<<( srlI(I,1) )<<endl;
  88. // K) -1 - correct
  89. // L) 2147483647 - correct
  90.  
  91. cout<<"M) "<<( sraC(C,1) )<<endl;
  92. cout<<"N) "<<( srlC(C,1) )<<endl;
  93. // M) 4294967295 - correct
  94. // N) 2147483647 - correct
  95.  
  96. }

在发布之前,我试图搜索这个问题,并没有发现任何提到这个bug。我也看了这里:What is the behaviour of shl and shr for non register sized operands?和这里:Arithmetic Shift Right rather than Logical Shift Right – 但有其他问题讨论(编译器内部转换任何类型到32位数字之前做实际的移位;或移位超过31位),但不是我的错误

但等等,这里是我的问题:@L_301_3@!

有一句话:他们说 –

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在发布这篇文章方面的帮助。伙计们,你摇滚!

解决方法

有一个bug,但它不是你的想法。这里是shr的 documentation

If x is a negative integer,the shl and shr operations are made clear
in the following example:

06000

因此,shr和shl总是逻辑移位而不是算术移位。

缺陷实际上是处理负的真实常数:

  1. Writeln('0) ',-1 shr 1 );

这里,-1是有符号值。它实际上有Shortint类型,一个有符号的8位整数。但移位运算符对32位值进行运算,因此符号扩展为32位值。这意味着这段摘录应该产生具有相同输出的两行:

  1. var
  2. i: Integer;
  3. ....
  4. i := -1;
  5. Writeln(-1 shr 1);
  6. Writeln( i shr 1);

并且输出应该是:

  1. 2147483647
  2. 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。你必须自己实现算术移位。我建议你这样使用内联汇编程序,因为我怀疑,最终可能更容易阅读和验证。

猜你在找的Delphi相关文章