检查字节数组值是否在 range/below 阈值内的最有效方法?

Most efficient way to check byte array values are within a range/below threshold?

我有一个 uint256 用作字节数组,由 10 个数字组成,每个数字 3 个字节(占用 30 个字节,32 个字节的前 2 个字节被忽略):

0x0000aaaaaabbbbbbccccccddddddeeeeeeffffff111111222222333333444444
  xxxx^     ^     ^     ^     ^     ^     ^     ^     ^     ^

我需要验证这些数字是否在一定范围内。它们是 uint24 所以它们总是正数,最低索引是 0 所以我真的只需要检查它们是否低于某个上限阈值。

目前我正在将相关字节读入 uint24 对象并检查数量是否低于阈值:

uint256 constant NUM_OF_GROUPS = 129600; // all numbers have to be between 0 and 129599

....... 

function decodeAndCheckGroupIndexes(uint256 x)
        public
        pure
        returns (
            uint24 a,
            uint24 b,
            uint24 c,
            uint24 d,
            uint24 e,
            uint24 f,
            uint24 g,
            uint24 h,
            uint24 i,
            uint24 j
        )
    {
        assembly {
            j := x
            mstore(0x1B, x)
            a := mload(0)
            mstore(0x18, x)
            b := mload(0)
            mstore(0x15, x)
            c := mload(0)
            mstore(0x12, x)
            d := mload(0)
            mstore(0x0F, x)
            e := mload(0)
            mstore(0x0C, x)
            f := mload(0)
            mstore(0x09, x)
            g := mload(0)
            mstore(0x06, x)
            h := mload(0)
            mstore(0x03, x)
            i := mload(0)
        }
        require(
            a < NUM_OF_GROUPS &&
                b < NUM_OF_GROUPS &&
                c < NUM_OF_GROUPS &&
                d < NUM_OF_GROUPS &&
                e < NUM_OF_GROUPS &&
                f < NUM_OF_GROUPS &&
                g < NUM_OF_GROUPS &&
                h < NUM_OF_GROUPS &&
                i < NUM_OF_GROUPS &&
                j < NUM_OF_GROUPS,
            "group is out of range"
        ); 
    }

但是我想知道是否有更好的方法来检查它并减少计算量?此检查发生在一个包含 uint256 数组的循环中,因此我试图实现最大效率以降低 gas 成本。

129,600 的 24 位无符号二进制表示是 000000011111101001000000,它的最左边 7 位全为 0。

检查 24 位数字

如果一个 24 位无符号数的最左边 8 位全为 0,则它小于或等于 65,535,因此小于 129,600

如果一个 24 位无符号数的最左边 7 位为 1,则它大于或等于 131,072,因此大于 129,600

这可以用来检查一个24位无符号数是否肯定通过或肯定不小于129,600

如果数字和 0xFF0000 之间的按位 and 为零,则该数字通过检查(小于 129,600)。

否则,如果数字和 0xFE0000 之间的按位 and 非零,则该数字未通过检查(大于 129,600)。

否则可以直接和129,600进行比较。

将检查扩展到十个 24 位数字

对于编码 10 个 24 位数字的 256 位 uint256 值——如问题中所述——可以对所有 10 个数字进行前两个检查(肯定通过或肯定失败)中的每一个具有 256 位掩码和相应的按位 and.

如果 uint256 值和以下掩码之间的按位 and 为零,则这 10 个数字通过检查,因为其中 none 个数字的 8 个中有 1最左边的位(所有数字都小于或等于 65,535,因此小于 129,600)。

0x0000FF0000FF0000FF0000FF0000FF0000FF0000FF0000FF0000FF0000FF0000
  xxxx^     ^     ^     ^     ^     ^     ^     ^     ^     ^

否则,如果 uint256 值和后面的掩码之间的按位 and 非零,则数字无法通过检查,因为至少有一个数字在其 7 中有一个 1最左边的位(至少有一个数字大于或等于 131,072,因此大于 129,600)。

0x0000FE0000FE0000FE0000FE0000FE0000FE0000FE0000FE0000FE0000FE0000
  xxxx^     ^     ^     ^     ^     ^     ^     ^     ^     ^

否则,可以使用问题中提出的方法将 10 个数字直接与 129,600 进行比较。

总结

这种方法提供了一种快速检查是否所有 10 个数字都肯定通过或至少一个数字肯定失败的方法。在没有明确通过或明确失败的情况下,可以使用计算要求更高的程序来单独检查 10 个数字中的每一个(失败时短路)。

伪代码

(&用于按位表示and)

uint256 valid_mask = 0x0000FF0000FF0000FF0000FF0000FF0000FF0000FF0000FF0000FF0000FF0000;
uint256 invalid_mask = 0x0000FE0000FE0000FE0000FE0000FE0000FE0000FE0000FE0000FE0000FE0000;

// returns true if all ten numbers encoded in `input` are
// less than 129,600
bool less_than_129600(uint256 input) {
    // check if all ten numbers are definitely valid
    if (valid_mask & input == 0)
        return true;
    
    // check if at least one number is definitely invalid
    if (invalid_mask & input != 0)
        return false;

    // check each number and return false if an invalid number
    // is encountered
    ...

    // if we haven't returned after the last check, all numbers
    // are valid
    return true;
}