2

I frequently cast unsigned integer types (e.g. uint8) to signed integer types (e.g. int24), this requires 2 casts first to the unsigned type (e.g. uint24) then to the signed type (int24). I know I could store that variable as an int24 to avoid this casting to begin with, but my understanding is that computation is generally cheaper than storage especially since I've bundled the uint8 in a state struct, so in my context to read a uint8 from storage and cast it to an int24 would be cheaper than simply reading an int24 from storage. Is this understanding correct and if so what would be the relative gas savings between the 2 methods?

An oversimplified example of the 2 methods:

// method 1
struct State {
  uint8 myNumber;
  int24 myInteger;
}

function doSomething() {
  return myInteger + int24(uint24(myNumber));
}

// method 2
struct State {
  int24 myNumber;
  int24 myInteger;
}

function doSomething() {
  return myInteger + myNumber;
}
1
  • I think the best answer to this would be to test your scenario. I was trying to find an answer in official Solidity documentation, but there was no word of it.
    – Sky
    Commented Feb 3, 2022 at 7:43

2 Answers 2

3

Short version, it depends, but you can expect between 42 and 46 gas, when tested with Solidity 0.8.7+commit.e28d00a7.

General thoughts - casting to int/uint is cheapest, it costs ~2 gas to cast between signed/unsigned. There may be some conflating factors here as well, but these are the results I got testing a single function, reading a state variable and directly returning it:

contract TestCasting {
    int128 testValue = 123;

    function aaaa() public view returns (int) {
        return testValue;
    }
    // 2,415 uint => uint
    // 2,415 int => uint
    // 2,457 uint8 => uint
    // 2,457 uint64 => uint
    // 2,457 uint192 => uint

    // 2,459 int8 => uint
    // 2,459 int128 => uint

    // 
    // 2,459 int128 => uint128
    // 2,457 uint128 => uint128
    // 2,459 uint128 => int128
    // 2,461 int128 => int128 // WTF?

    // 
    // 2,469 int8 => int128

    // 2,459 uint8 => int
    // 2,461 int8 => int
    // 2,461 int128 => int
}

Final thoughts: The cost of a cast works out to about ~0.1c on mainnet, or 0.00012c on Polygon. If you spend an hour optimizing your casts and removed 1 cast every 3 minutes, that work would require ~19 million transactions to pay for itself.

In other words, it's probably not really worth worrying about.

-1
+50

I wasn't able to find relevant documentation so tested out the gas consumption of the functions.


struct State1 {
    uint8 myNumber;
    int24 myInteger;
}

struct State2 {
    int24 myNumber;
    int24 myInteger;
}

State1 state1 = State1(1, 1);
State2 state2 = State2(1, 1);

function test1() public view returns (int24) {
    return state1.myInteger + int24(uint24(state1.myNumber));
}
// gas consumed : 556

function test2() public view returns (int24) {
    return state2.myInteger + state2.myNumber;
}
// gas consumed : 564

We can see that even test1 consumes less gas than test2. So from a transaction gas cost point of view, type conversion is better in this case.

1
  • 1
    you pay 22 more gas because the method id of test2 comes after test1..
    – Chiray
    Commented Aug 17, 2022 at 12:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.