Awesome! That is a great way to learn about the EVM.
The answer may be verbose but it will be complete.
Two's complement is a way to represent integers in binary. It enables us to easily and simply express signed integers (pasted for convenience; see source for more details):
Suppose we're working with 8 bit quantities (for simplicity's sake) and suppose we want to find how -28 would be expressed in two's complement notation. First we write out 28 in binary form.
Then we invert the digits. 0 becomes 1, 1 becomes 0.
Then we add 1.
That is how one would write -28 in 8 bit binary.
Extending Length of a Signed Integer
The concept of extending the length of an unsigned integer is rather trivial, for instance:
Take the number 01010101 for example. It is 1 byte in length.
Suppose that we want to represent it as 2 bytes. All we need to do is add eight 0's to our number like so: 00000000 01010101. Now it is 2 bytes.
However, the left most bit of a signed integer is the sign bit, we must take care to include the sign bit when extended a signed integer or we will alter it's original value. Let's say I wanted to extend the number we used above -28 in binary 11100100 that same way we extended the unsigned integer, like so:
Now our signed integer is no longer -28 but rather 228. Thus, we need a mechanism for extending the signed integer while preserving the sign bit. It's not that actually, all we need to do is instead of appending eight 0's, we'll append eight of the sign bits. In this case the sign bit is 1. So we'll append eight 1's 1 like so:
Now we have the value -28 expressed in two bytes instead of 1.
See source for more details.
Why is there a special opcode for this? This could get low level very fast but the process of extending the sign of an signed integer would need several opcodes. I'll briefly explain how I would do it without the SIGNEXTEND opcode:
- Bit mask to get the left most bit
- Check value of left most bit
- Append the value of the left most bit
Because of this and because it is used frequently enough the developers must have thought it necessary to create a special opcode for it.
And why would we use it? This is an educated guess but one might use it to move data between different size registers as this article would suggest.
Let me know if you found this helpful! 😃