PHP 中的无符号右移 / 零填充右移 / >>> (相当于 Java/JavaScript)
Unsigned Right Shift / Zero-fill Right Shift / >>> in PHP (Java/JavaScript equivalent)
在将其标记为重复之前,请阅读下文并检查 my code * my updated code!
所以我的问题是,我必须实现 Java/JavaScript '>>>'(无符号右移/零填充右移),但我无法使其以完全相同的方式工作。
我选择了我在 SO 和网络上找到的 11 个最有希望的实现(链接作为代码中的注释添加)并添加了一些测试用例。不幸的是,NONE 函数对所有测试返回了与 Java/JS 相同的响应。 (可能其中一些只适用于32位系统)
Live Code + JS+PHP 结果演示(点击运行):
http://phpfiddle.org/main/code/bcv7-bs2q *
http://phpfiddle.org/main/code/dpkw-rxfe
最接近的函数是:
//
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));
}
和
//
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 >>> -3) 和 * (32 >> 32) 上失败了,但是 只有 通过 (-3 >>> 0); shr11 在 (-3 >>> 0) 和 (32 >>> 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
,但在 [=65] =],它等于 -3
。不幸的是,这并没有改变我仍然无法获得任何功能来通过所有测试的事实。
*大更新:
自 PHP 7 起,负数位移位被认为是无效的,并导致:“致命错误:未捕获的 ArithmeticError:负数位移位 ” .据此,我认为我们不必通过那些测试,所以我更新了问题和代码。
因为我真的没有想法,所以我克隆了 Chromium V8 引擎和 Mozilla Central 存储库以获取 SpiderMonkey。我开始寻找 JS >>> 运算符,最后在 Mozilla 的代码中,我找到了一个将近 20 年的文件(从 1997 年开始),js/src/tests/ecma/Expressions/11.7.3.js,其中包含用于测试的无运算符代码 "zero-filling bitwise right shift operation"。在 PHP 重写后,这段代码通过了所有测试。
<?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 >>> 3 )
免责声明:我知道这甚至不接近 "professional" 解决方案,性能很差(4.33 秒,110,000 次循环;有问题的函数完成 ~0.04 秒,110,000 次循环), 也许这个片段中甚至还有一些不必要的功能,但目前我只有时间逐行实现它。如果有人有更好的解决方案、更好的性能或更清晰的代码,我将非常高兴。
在研究了问题中的两个函数("shr9"和"shr11")和merging/tweaking好的部分之后,我终于找到了解决方案。所有测试都通过了(我什至在演示中添加了更多),它也适用于负数的班次。
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
在将其标记为重复之前,请阅读下文并检查 my code * my updated code!
所以我的问题是,我必须实现 Java/JavaScript '>>>'(无符号右移/零填充右移),但我无法使其以完全相同的方式工作。
我选择了我在 SO 和网络上找到的 11 个最有希望的实现(链接作为代码中的注释添加)并添加了一些测试用例。不幸的是,NONE 函数对所有测试返回了与 Java/JS 相同的响应。 (可能其中一些只适用于32位系统)
Live Code + JS+PHP 结果演示(点击运行):
http://phpfiddle.org/main/code/bcv7-bs2q *
http://phpfiddle.org/main/code/dpkw-rxfe
最接近的函数是:
//
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));
}
和
//
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 >>> -3) 和 * (32 >> 32) 上失败了,但是 只有 通过 (-3 >>> 0); shr11 在 (-3 >>> 0) 和 (32 >>> 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
,但在 [=65] =],它等于 -3
。不幸的是,这并没有改变我仍然无法获得任何功能来通过所有测试的事实。
*大更新:
自 PHP 7 起,负数位移位被认为是无效的,并导致:“致命错误:未捕获的 ArithmeticError:负数位移位 ” .据此,我认为我们不必通过那些测试,所以我更新了问题和代码。
因为我真的没有想法,所以我克隆了 Chromium V8 引擎和 Mozilla Central 存储库以获取 SpiderMonkey。我开始寻找 JS >>> 运算符,最后在 Mozilla 的代码中,我找到了一个将近 20 年的文件(从 1997 年开始),js/src/tests/ecma/Expressions/11.7.3.js,其中包含用于测试的无运算符代码 "zero-filling bitwise right shift operation"。在 PHP 重写后,这段代码通过了所有测试。
<?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 >>> 3 )
免责声明:我知道这甚至不接近 "professional" 解决方案,性能很差(4.33 秒,110,000 次循环;有问题的函数完成 ~0.04 秒,110,000 次循环), 也许这个片段中甚至还有一些不必要的功能,但目前我只有时间逐行实现它。如果有人有更好的解决方案、更好的性能或更清晰的代码,我将非常高兴。
在研究了问题中的两个函数("shr9"和"shr11")和merging/tweaking好的部分之后,我终于找到了解决方案。所有测试都通过了(我什至在演示中添加了更多),它也适用于负数的班次。
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