solidity – Most Gas Efficient Way to Retrieve a Bit Value in Unsigned Integer

bit-manipulationsolidity

Say I have an uint16 _16bits with binary value 1110_1011_0001_0001, what would be the most gas efficient operation to get the value of any particular index in the range of [0, 15]?

For clarity:

using LowGasOperator for uint16;

 _16bits.getValue(0) == 1
 _16bits.getValue(1) == 0

What would be the most gas efficient definition of LowGasOperator.getValue?

Best Answer

I did some test with solc: 0.8.10+commit.fc410830.Emscripten.clang and the optimizer enabled (200 runs), and I came up with a function that is 45% less expensive than the double shift one you proposed:

// Gas cost: 103
function getValue(uint four_nibbles, uint mask) public pure returns (uint) {
    return (four_nibbles & mask) > 0 ? 1 : 0;
}

To squeeze a lot more you can do this:

// Gas cost: 27
function getValue(uint four_nibbles, uint mask) public pure returns (bool) {
    return (four_nibbles & mask) > 0;
}

But it depends on your use case if you can adapt your business logic accordingly.

These functions use a bitwise AND operator (&) with a mask instead of an index, so for example to get the 1st bit you pass 1, the 2nd bit you pass 2, the 3rd bit you pass 4, the 4th bit you pass 8, and so on.

Please note that using unit16 and in general, any uint smaller than 256 bits, can easily increase the cost of the overall operations. The EVM always works with variables made of 256 bits, so when you use different sizes you introduce a surplus of work, and the gain from using smaller numbers rarely compensates for the added work. Check always on the field if the cost is really reduced.

For example in your specific case, the double shift function costs 188 gas with uint16 and it costs 142 gas with uint (that is an alias for uint256):

// Gas cost: 142
function getValue(uint four_nibbles, uint index) public pure returns (uint) {
    return (four_nibbles << (255 - index)) >> 255;
}

// Gas cost: 188
function getValue(uint16 four_nibbles, uint16 index) public pure returns (uint16) {  
    return (four_nibbles << (15 - index)) >> 15;
}

I created a dedicated repo for you if you want to test it by yourself. Please don't take it as an example of good programming, it is just a very raw and fast way to test gas costs ;)