无符号右移/零填充右移/>>>在PHP(Java / JavaScript等价物)

时间:2016-12-14 03:23:11

标签: javascript java php bit-manipulation bit-shift

在将此标记为重复之前,请阅读以下内容,并检查 my code * my updated code

所以我的问题是,我必须实现Java / JavaScript'>>>' (无符号右移/零填充右移),但我无法以完全相同的方式工作。

我选择了我在SO和网络上找到的11个最有希望的实现(链接在代码中添加为注释)并添加了一些测试用例。不幸的是, NONE 函数返回了与所有测试相同的Java / JS响应。 (可能其中一些只在32位系统上工作)

实时代码+ JS + PHP结果演示(点击运行):
http://phpfiddle.org/main/code/bcv7-bs2q *
http://phpfiddle.org/main/code/dpkw-rxfe

最接近的功能是:

// http://stackoverflow.com/a/27263298
function shr9($a,$b) { 
    if($a>=0) return $a>>$b;
    if($b==0) return (($a>>1)&0x7fffffff)*2+(($a>>$b)&1);
    return ((~$a)>>$b)^(0x7fffffff>>($b-1)); 
}

// http://stackoverflow.com/a/25467712
function shr11($a, $b) { 
    if ($b > 32 || $b < -32) {
        $m = (int)($b/32);
        $b = $b-($m*32);
    }

    if ($b < 0)
        $b = 32 + $b;

    if ($a < 0) 
    { 
        $a = ($a >> 1); 
        $a &= 2147483647; 
        $a |= 0x40000000; 
        $a = ($a >> ($b - 1)); 
    } else { 
        $a = ($a >> $b); 
    } 
    return $a; 
}

不幸的是,shr9在(-10&gt;&gt;&gt; -3)和 *(32&gt;&gt; 32)上失败,但是通过(-3>&gt;&gt; 0);并且shr11失败(-3&gt;&gt;&gt; 0)以及(32&gt;&gt;&gt; 32)。

测试用例:

         0 >>> 3    == 0 
         3 >>> 0    == 3 
         0 >>> -3   == 0 
        -3 >>> 0    == 4294967293 (in JS); -3 (in Java)  
        10 >>> 3    == 1 
        10 >>> -3   == 0 
       -10 >>> 3    == 536870910 
       -10 >>> -3   == 7 
-672461345 >>> 25   == 107 
        32 >>> 32   == 32 
       128 >>> 128  == 128 

修改:我发现-3 >>> 0仅在JavaScript (为什么?)中等于4294967293,但在Java中,它等于-3。不幸的是,这并没有改变我仍然无法通过所有测试的任何功能的事实。

* BIG UPDATE:

自PHP 7起,按负数位移位被视为无效并导致:“致命错误:未捕获的算术错误:按负数移位”。根据这一点,我认为我们不必通过这些测试,所以我已经更新了问题和代码。

2 个答案:

答案 0 :(得分:4)

在查看问题(“shr9”和“shr11”)中的两个函数并合并/调整好的部分之后,我终于找到了解决方案。所有测试都通过了(我甚至在演示中添加了更多),它也适用于负数的移位。

[Live Demo]

function unsignedRightShift($a, $b) {
    if ($b >= 32 || $b < -32) {
        $m = (int)($b/32);
        $b = $b-($m*32);
    }

    if ($b < 0) {
        $b = 32 + $b;
    }

    if ($b == 0) {
        return (($a>>1)&0x7fffffff)*2+(($a>>$b)&1);
    }

    if ($a < 0) 
    { 
        $a = ($a >> 1); 
        $a &= 0x7fffffff; 
        $a |= 0x40000000; 
        $a = ($a >> ($b - 1)); 
    } else { 
        $a = ($a >> $b); 
    } 
    return $a; 
}

此代码不仅准确,而且速度快。
基准测试结果:100000次循环:0.25秒
基准测试:http://phpfiddle.org/main/code/mj68-1s7e

答案 1 :(得分:2)

由于我真的没有想法,我克隆了Chromium V8引擎和Mozilla Central回购以获得SpiderMonkey。我开始搜索JS&gt;&gt;&gt;运算符,最后在Mozilla的代码中,我找到了一个近20年的文件(从1997年开始),js/src/tests/ecma/Expressions/11.7.3.js,其中包含用于测试“零填充按位右移操作”的无操作符代码。在PHP中重写后,此代码通过了所有测试。

[LiveDemo]

<?php

function ToInteger( $n ) {
  $sign = ( $n < 0 ) ? -1 : 1;

  if ( $n != $n ) {
    return 0;
  }
  if ( abs( $n ) == 0 || abs( $n ) == INF ) {
    return $n;
  }
  return intval( $sign * floor(abs($n)) );
}

function ToInt32( $n ) {
  $sign = ( $n < 0 ) ? -1 : 1;

  if ( abs( $n ) == 0 || abs( $n ) == INF) {
    return 0;
  }

  $n = ($sign * floor( abs($n) )) % pow(2,32);
  $n = ( $n >= pow(2,31) ) ? $n - pow(2,32) : $n;

  return ( $n );
}

function ToUint32( $n ) {
  $sign = ( $n < 0 ) ? -1 : 1;

  if ( abs( $n ) == 0 || abs( $n ) == INF) {
    return 0;
  }

  $n = $sign * floor( abs($n) );
  $n = $n % pow(2,32);

  if ( $n < 0 ){
    $n += pow(2,32);
  }

  return ( $n );
}

function ToUint16( $n ) {
  $sign = ( $n < 0 ) ? -1 : 1;

  if ( abs( $n ) == 0 || abs( $n ) == INF) {
    return 0;
  }

  $n = ( $sign * floor( abs($n) ) ) % pow(2,16);

  if ($n <0) {
    $n += pow(2,16);
  }

  return ( $n );
}

function Mask( $b, $n ) {
  $b = ToUint32BitString( $b );
  $b = substr( $b, strlen($b) - $n );
  $b = ToUint32Decimal( $b );
  return ( $b );
}

function ToUint32BitString( $n ) {
  $b = "";
  for ( $p = 31; $p >=0; $p-- ) {
    if ( $n >= pow(2,$p) ) {
      $b .= "1";
      $n -= pow(2,$p);
    } else {
      $b .= "0";
    }
  }
  return $b;
}

function ToInt32BitString( $n ) {
  $b = "";
  $sign = ( $n < 0 ) ? -1 : 1;

  $b .= ( $sign == 1 ) ? "0" : "1";

  for ( $p = 30; $p >=0; $p-- ) {
    if ( ($sign == 1 ) ? $sign * $n >= pow(2, $p) : $sign * $n > pow(2,$p) ) {
      $b .= ( $sign == 1 ) ? "1" : "0";
      $n -= $sign * pow( 2, $p );
    } else {
      $b .= ( $sign == 1 ) ? "0" : "1";
    }
  }

  return $b;
}

function ToInt32Decimal( $bin ) {
  $r = 0;
  $sign;

  if ( intval($bin[0]) == 0 ) {
    $sign = 1;
    $r = 0;
  } else {
    $sign = -1;
    $r = -(pow(2,31));
  }

  for ( $j = 0; $j < 31; $j++ ) {
    $r += pow( 2, $j ) * intval($bin[31-$j]);
  }

  return $r;
}

function ToUint32Decimal( $bin ) {
  $r = 0;


  for ( $l = strlen($bin); $l < 32; $l++ ) {
    $bin = "0" . $bin;
  }

  for ( $j = 0; $j < 32; $j++ ) {
    $r += pow( 2, $j ) * intval($bin[31-$j]);

  }

  return $r;
}

function RShift( $s, $a ) {
  $s = ToUint32BitString( $s );
  for ( $z = 0; $z < $a; $z++ ) {
    $s = "0" . $s;
  }
  $s = substr( $s, 0, strlen($s) - $a );

  return ToUint32Decimal($s);
}

function UnsignedRightShift( $s, $a ) {
  $s = ToUint32( $s );
  $a = ToUint32( $a );
  $a = Mask( $a, 5 );
  return ( RShift( $s, $a ) );
}

用法示例: UnsignedRightShift(10, 3);(= 10&gt;&gt;&gt; 3)

免责声明:我知道这甚至不接近“专业”解决方案,性能不好(4.33s,110,000循环;有问题的功能完成~0.04s,110,000循环),也许有甚至在这个片段中不必要的功能,但目前我只有时间逐行实现它。如果有人有更好的解决方案,更好的性能或更清晰的代码,我很高兴看到它。