php如何实现无符号右移

php怎么实现无符号右移,类似js的>>>
回复列表(25|显示机器人聊天)
  • @Ta / 2021-08-22 / /

    @幕后导演,可以写函数实现,比如:

    // 32位无符号右移
    function unsignedRight32($a, $n) {
        $c = 2147483647 >> ($n - 1);
        return $c & ($a >> $n);
    }
    
    // 64位无符号右移
    function unsignedRight64($a, $n) {
        if (PHP_INT_MAX == 2147483647) {
            throw new Exception('32位PHP不支持64位整数');
        }
        $c = PHP_INT_MAX >> ($n - 1);
        return $c & ($a >> $n);
    }
    
  • @Ta / 2021-08-22 / /

    32位和64位负整数的无符号右移结果是不同的,因为一个符号位在第32位,一个符号位在第64位。右移出来的肯定是两个完全不同的正整数。

    而PHP的32位版使用32位整数,64位版使用64位整数。如果它直接提供无符号右移运算符,32位版和64位版的结果就会不同,就会引起困惑。所以它没有提供。

    自行实现的右移操作可以用不同的函数模拟32位和64位右移结果。


    js中所有数值都是double类型,位运算时转为32位整数进行操作,所以结果是确定的,没有这种困扰。

    java/c/c++中你可以自行指定整数位宽,所以也没有这种困扰。

  • @Ta / 2021-08-22 / /
    @老虎会游泳,就是32位和64位结果不同,导致我服务器和本地计算出来的不一样,很是惆怅
  • @Ta / 2021-08-22 / /

    @幕后导演,在64位PHP中,使用pack和unpack函数就能把负数数值转换成32位无符号数值:

    function uint32($int) {
        if (PHP_INT_MAX == 0x7fffffff) {
            throw new Exception('32位PHP不支持64位整数');
        }
        return unpack('P', pack("VV", $int, 0))[1] & 0xffffffff;
    }
    
    var_dump(uint32(-1));
    

    然后你就可以使用普通位移运算符进行运算了,左移、加、乘时都加上 & 0xffffffff 就可以保证结果始终在32位整数范围内。

    $a = uint32(-1);
    $b = uint32(33333333);
    $c = (($a >> 4) + (($b << 20) & 0xffffffff)) & 0xffffffff;
    var_dump($a, $b, $c);
    

    得到:

    int(4294967295)
    int(33333333)
    int(357564415)
    

    和js的结果一致:

    console.log((-1 >>> 4) + (33333333 << 20));
    // 357564415
    
  • @Ta / 2021-08-22 / /

    @幕后导演,我发现直接用负数& 0xffffffff就能把它转成uint32,不知道是不是特例:

    $a = -1 & 0xffffffff;
    $b = 33333333;
    $c = (($a >> 4) + (($b << 20) & 0xffffffff)) & 0xffffffff;
    var_dump($a, $b, $c);
    

    得到:

    int(4294967295)
    int(33333333)
    int(357564415)
    
  • @Ta / 2021-08-22 / /

    减法也要& 0xffffffff来保证结果为32位无符号整数,因为结果有可能为负

    var_dump(1 - 2);
    var_dump((1 - 2) & 0xffffffff);
    

    得到

    int(-1)
    int(4294967295)
    

    除法进行& 0xffffffff可以消除小数点:

    var_dump(4 / 3);
    var_dump((4 / 3) & 0xffffffff);
    

    得到:

    float(1.3333333333333)
    int(1)
    

    所以,对所有操作数和每一步运算结果都进行& 0xffffffff,就可以在64位PHP里模拟32位无符号运算了。

  • @Ta / 2021-08-22 / /

    如果要把结果转成有符号整数,可以这样做:

    function sint32($int) {
        if (PHP_INT_MAX == 0x7fffffff) {
            throw new Exception('32位PHP不支持64位整数');
        }
        return unpack('l', pack("L", $int))[1];
    }
    
    var_dump(sint32(0xffff0000));
    

    得到:

    int(-65536)
    
  • @Ta / 2021-08-22 / /
    @老虎会游泳,已经解决啦,我服务器上是64位,在本地测试是32位,所以结果不一样,将字符串长度超出则截取底32位,长度不够,则填充高位为0到32位
    function shr32($x, $bits) {
        if ($bits >= 32) {
            return 0;
        } 
        // 转换成代表二进制数字的字符串
        $bin = decbin($x);
        $l = strlen($bin); 
        // 字符串长度超出则截取底32位,长度不够,则填充高位为0到32位
        if ($l > 32) {
            $bin = substr($bin, $l - 32, 32);
        } elseif ($l < 32) {
            $bin = str_pad($bin, 32, '0', STR_PAD_LEFT);
        } 
        // 取出要移动的位数,并在左边填充0
        return bindec(str_pad(substr($bin, 0, 32 - $bits), 32, '0', STR_PAD_LEFT));
    }
  • @Ta / 2021-08-22 / /

    @幕后导演思路巧妙

  • @Ta / 2021-08-22 / /
    @老虎会游泳,那么问题来了,php64位使用>>和32位的结果又不一样了
    35393633961922>>2
    php 计算结果是8848408490480
    js 计算结果是-297881104
    当php打印1000>>8 = 3,js打印出来同样是3
  • @Ta / 2021-08-22 / /

    @幕后导演,35393633961922超出了int32的范围,所以它要先被转换为一个int32,然后再位移才行。

    function sint32($int) {
        return unpack('l', pack("L", $int))[1];
    }
    
    var_dump(sint32(35393633961922) >> 2);
    

    得到 int(-297881104)

  • @Ta / 2021-08-22 / /
    @老虎会游泳
    35393633961922 << 28 
    在js打印结果是536870912
    用上面你给的函数结果是-319847399407222784
  • @Ta / 2021-08-22 / /

    @幕后导演,结果超出了int32,你要对这个结果再次使用sint32。

    function sint32($int) {
        return unpack('l', pack("L", $int))[1];
    }
    
    var_dump(sint32(sint32(35393633961922) << 28));
    
  • @Ta / 2021-08-22 / /

    @幕后导演,如果结果可能大于2147483647,或者小于-2147483648,就应该对其调用sint32

  • @Ta / 2021-08-22 / /
    @老虎会游泳
    那么问题又来了,对于php异或运算和js异或运算的结果出来不一样
    35393633961922 ^ 3963386674
    js 打印结果是1422310640
    php 打印出来结果是35391952829680
    很是迷惑
  • @Ta / 2021-08-22 / /

    @幕后导演

    function sint32($int) {
        return unpack('l', pack("L", $int))[1];
    }
    
    var_dump(sint32(sint32(35393633961922) ^ sint32(3963386674)));
    

    int(1422310640)

  • @Ta / 2021-08-23 / /

    @幕后导演,对了,32位无符号右移还可以写成这样:

    function shr32($x, $bits) {
        if (PHP_INT_MAX == 0x7fffffff) {
            throw new Exception('32位PHP不支持64位整数');
        }
        return ($x & 0xffffffff) >> $bits;
    }
    
    var_dump(shr32(-1, 2));
    

    int(1073741823),结果和js一致。

    比你的简单很多。

  • @Ta / 2021-08-22 / /

    这么多年的php白写了,看不懂

  • @Ta / 2021-08-23 / /
    @老虎会游泳,已经搞定了,在64位上能算准确,但是放到32位上的机器又算不准确了,所以搞了两份代码,感谢大佬
添加新回复
回复需要登录