【Solidity】位运算及其应用

位运算

位运算是一种对二进制数据进行操作的技术。

常用的位运算符

  1. 与运算(AND)a & b:对应位都为 1 时,结果为 1,否则为 0

  2. 或运算(OR)a | b:对应位只要有一个为 1,结果为 1

  3. 异或运算(XOR)a ^ b:对应位不同,结果为 1,相同为 0

  4. 非运算(NOT)~a:将每个位取反,0 变 1,1 变 0

  5. 左移运算(Left Shift)a << n:将二进制位向左移动指定的位数,右侧补 0

  6. 右移运算(Right Shift)a >> n:将二进制位向右移动指定的位数,左侧补 0

基础示例

contract Demo {
    // 与运算
    function andOperation(uint8 a, uint8 b) public pure returns (uint8) {
        return a & b;
    }

    // 或运算
    function orOperation(uint8 a, uint8 b) public pure returns (uint8) {
        return a | b;
    }

    // 异或运算
    function xorOperation(uint8 a, uint8 b) public pure returns (uint8) {
        return a ^ b;
    }

    // 非运算
    function notOperation(uint8 a) public pure returns (uint8) {
        return ~a;
    }

    // 左移运算
    function leftShiftOperation(uint8 a, uint8 n) public pure returns (uint8) {
        return a << n;
    }

    // 右移运算
    function rightShiftOperation(uint8 a, uint8 n) public pure returns (uint8) {
        return a >> n;
    }
}
  1. 与运算示例:5 & 3,二进制表示为 0101 & 0011,结果为 0001,即 1

  2. 或运算示例:5 | 3,二进制表示为 0101 | 0011,结果为 0111,即 7

  3. 异或运算示例:5 ^ 3,二进制表示为 0101 ^ 0011,结果为 0110,即 6

  4. 非运算示例:~5,二进制表示为 ~00000101(对于 8 位无符号整数),结果为 11111010,即 250

  5. 左移运算示例:5 << 1,二进制表示为 0101 << 1,结果为 1010,即 10

  6. 右移运算示例:5 >> 1,二进制表示为 0101 >> 1,结果为 0010,即 2



应用示例

将某数值转为二进制表示

contract Demo {
    // 将无符号整数转换为二进制字符串表示
    function toBinary(uint num) public pure returns (string memory) {
        // 如果输入数字为 0, 直接返回字符串 "0"
        if (num == 0) {
            return "0";
        }

        // 计算二进制表示所需的位数
        uint length = 0;
        uint temp = num;
        while (temp > 0) {
            length++;
            temp >>= 1; // 将 temp 右移一位, 相当于除以 2
        }

        // 创建一个长度为 length 的字节数组, 用于存储二进制表示
        bytes memory binary = new bytes(length);

        // 循环遍历每一位
        for (uint i = 0; i < length; i++) {
            // (1 << i) 将 1 左移 i 位, 得到一个只有第 i + 1 位为 1 的数
            // num & (1 << i) 能检查 num 的第 i + 1 位是否为 1
            // 如果结果不为 0, 则第 i 位为 1, 否则为 0
            // eg: i = 3, 1 << 3 = 00001000, 00001101 & 00001000 = 00001000 != 0
            binary[length - i - 1] = (num & (1 << i)) != 0
                ? bytes1("1")
                : bytes1("0");
        }

        // 将字节数组转换为字符串并返回
        return string(binary);
    }
}

获取某二进制数值的最后 n 位:eg:x = 00001101 = 13getLastBits(x, 3) = 00000101 = 5

contract Demo {
    // 思路 1: 定义一个掩码, 掩码的最后 n 位为 1, 其余位为 0, 与原数值进行 & 运算
    function getLastBits(uint8 x, uint8 n) public pure returns (uint8) {
        // 生成掩码:
        // (1 << n) 将 1 左移 n 位, 得到一个只有第 n + 1 位为 1 的数
        // (1 << n) - 1 得到一个只有最后 n 位为 1 的数
        // eg: n = 3, 1 << 3 = 00001000, 00001000 - 1 = 00000111
        uint8 mask = (uint8(1) << n) - uint8(1);
        return x & mask;
    }

    // 思路 2: 定义一个除数, 除数的倒数第 n + 1 位为 1, 其余位为 0, 对原数值进行 % 运算
    function getLastBits2(uint8 x, uint8 n) public pure returns (uint8) {
        // 生成除数: 2 ** n 或 1 << n
        // eg: n = 3, 2 ** 3 = 8 = 00001000; 1 << 3 = 00001000
        uint8 divisor = uint8(1) << n;
        return x % divisor;
    }
}

查找最高有效位:最高有效位(Most Significant Bit,MSB)是二进制表示中最左边的 1 所在的位置。可以使用位运算来查找一个数值的最高有效位。

contract Demo {
    // 查找 uint 数值的最高有效位; eg: 42 -> 101010 -> 5; 10 -> 001010 -> 3
    function findMSBByIf(uint value) public pure returns (uint8) {
        uint8 msb;
        if (value >= 2 ** 128) {
            value >>= 128;
            msb += 128;
        }
        if (value >= 2 ** 64) {
            value >>= 64;
            msb += 64;
        }
        if (value >= 2 ** 32) {
            value >>= 32;
            msb += 32;
        }
        if (value >= 2 ** 16) {
            value >>= 16;
            msb += 16;
        }
        if (value >= 2 ** 8) {
            value >>= 8;
            msb += 8;
        }
        if (value >= 2 ** 4) {
            value >>= 4;
            msb += 4;
        }
        if (value >= 2 ** 2) {
            value >>= 2;
            msb += 2;
        }
        if (value >= 2 ** 1) {
            // value >>= 1; 最后一个就不需要做位运算啦
            msb += 1;
        }
        return msb;
    }
}

我们可以用 assembly 优化上例,以节省 gas:

contract Demo {
    function findMSBByIfInAsm(uint value) public pure returns (uint8) {
        uint8 msb;
        assembly {
            // 如果 value 大于 2^128 - 1
            if gt(value, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) {
                value := shr(128, value) // 右移 128 位
                msb := add(msb, 128) // msb 加 128
            }
            // 如果 value 大于 2^64 - 1
            if gt(value, 0xFFFFFFFFFFFFFFFF) {
                value := shr(64, value) // 右移 64 位
                msb := add(msb, 64) // msb 加 64
            }
            // 如果 value 大于 2^32 - 1
            if gt(value, 0xFFFFFFFF) {
                value := shr(32, value) // 右移 32 位
                msb := add(msb, 32) // msb 加 32
            }
            // 如果 value 大于 2^16 - 1
            if gt(value, 0xFFFF) {
                value := shr(16, value) // 右移 16 位
                msb := add(msb, 16) // msb 加 16
            }
            // 如果 value 大于 2^8 - 1
            if gt(value, 0xFF) {
                value := shr(8, value) // 右移 8 位
                msb := add(msb, 8) // msb 加 8
            }
            // 如果 value 大于 2^4 - 1
            if gt(value, 0xF) {
                value := shr(4, value) // 右移 4 位
                msb := add(msb, 4) // msb 加 4
            }
            // 如果 value 大于 2^2 - 1
            if gt(value, 0x3) {
                value := shr(2, value) // 右移 2 位
                msb := add(msb, 2) // msb 加 2
            }
            // 如果 value 大于 2^1 - 1
            if gt(value, 0x1) {
                // value := shr(1, value) 最后一个就不需要做位运算啦
                msb := add(msb, 1) // msb 加 1
            }
        }
        return msb;
    }
}

我们可以使用循环来简化上例(但本例使用循环会消耗更多的 gas):

contract Demo {
    function findMSBByLoop(uint value) public pure returns (uint8) {
        uint8 msb
        while (value > 0) {
            value >>= 1; // 将 value 右移一位 (相当于除以 2)
            msb++; // msb 加 1
        }
        return msb - 1; // 返回 msb 减 1, 因为最后一次循环多加了一次
    }
}

使用 assembly 版本:

contract Demo {
    function findMSBByLoopInAsm(uint value) public pure returns (uint8) {
        uint8 msb;
        assembly {
            // 初始化 for 循环, 不需要初始化语句
            for { } gt(value, 0) { msb := add(msb, 1) } {
                // 循环条件: value > 0
                // 每次循环结束时, msb 加 1
                // 循环体: 将 value 右移一位(相当于除以 2)
                value := div(value, 2)
            }
            // 循环结束后, msb 减 1, 因为最后一次循环多加了一次
            msb := sub(msb, 1)
        }
        return msb;
    }
}

其他实现方式:

contract Demo {
    function findMSBInAsm(uint value) public pure returns (uint8) {
        uint8 msb;
        assembly {
            let f := shl(7, gt(value, 0xffffffffffffffffffffffffffffffff))
            // gt(value, 0xffffffffffffffffffffffffffffffff) 表示 value > 2^128 - 1 ? 1 : 0
            // shl(7, gt(value, 0xffffffffffffffffffffffffffffffff)) 表示 value > 2^128 - 1 ? (1 << 7) : (0 << 7)
            // 1 << 7 = 128, 0 << 7 = 0
            value := shr(f, value)
            // value >>= 128 or value >>= 0
            msb := or(msb, f)
            // 对于没有进位的简单加法场景, 可用 or 替代 add 以节省 gas
            // eg: 0110 + 0001 = 0111, 0110 | 0001 = 0111
            // 所以这里表示  msb += 128 or msb += 0
        }
        assembly {
            let f := shl(6, gt(value, 0xffffffffffffffff))
            // gt(value, 0xffffffffffffffff) 表示 value > 2^64 - 1 ? 1 : 0
            // shl(6, gt(value, 0xffffffffffffffff)) 表示 value > 2^64 - 1 ? (1 << 6) : (0 << 6)
            // 1 << 6 = 64, 0 << 6 = 0
            value := shr(f, value)
            // value >>= 64 or value >>= 0
            msb := or(msb, f)
            // msb += 64 or msb += 0
        }
        assembly {
            let f := shl(5, gt(value, 0xffffffff))
            // gt(value, 0xffffffff) 表示 value > 2^32 - 1 ? 1 : 0
            // shl(5, gt(value, 0xffffffff)) 表示 value > 2^32 - 1 ? (1 << 5) : (0 << 5)
            // 1 << 5 = 32, 0 << 5 = 0
            value := shr(f, value)
            // value >>= 32 or value >>= 0
            msb := or(msb, f)
            // msb += 32 or msb += 0
        }
        assembly {
            let f := shl(4, gt(value, 0xffff))
            value := shr(f, value)
            msb := or(msb, f)
        }
        assembly {
            let f := shl(3, gt(value, 0xff))
            value := shr(f, value)
            msb := or(msb, f)
        }
        assembly {
            let f := shl(2, gt(value, 0xf))
            value := shr(f, value)
            msb := or(msb, f)
        }
        assembly {
            let f := shl(1, gt(value, 0x3))
            value := shr(f, value)
            msb := or(msb, f)
        }
        assembly {
            let f := gt(value, 0x1)
            // value := shr(f, value) 最后一个就不需要做位运算啦
            msb := or(msb, f)
        }
        return msb;
    }
}

可见各方法及消耗的 gas 如下: findMSBByIf (1200)、findMSBByLoop (1662)、findMSBByIfInAsm (883)、findMSBByLoopInAsm (908)、findMSBInAsm (1010)



  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JS.Huang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值