You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
├────\underset{\hskip -3.5em - 2^{255}}─────────\underset{\hskip -0.4 em -1}{─}┤
176
176
}
177
177
$$
@@ -184,11 +184,11 @@ $$
184
184
```
185
185
186
186
The maximum positive integer that can be represented in a two's complement system using 256 bits is
187
-
`0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff` which is roughly equal to half of the maximum value that can be represented using uint256.
187
+
`0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff` which is roughly equal to half of the maximum value that can be represented using `uint256`.
188
188
The most significant bit of this number is `0`, while all other bits are `1`.
189
189
190
190
On the other hand, all negative numbers start with a `1` as their first bit.
191
-
If we look at the underlying hex representation of these numbers, they are all greater than or equal to the smallest integer that can be represented using int256, which is `0x8000000000000000000000000000000000000000000000000000000000000000`. The integer's binary representation is a `1` followed by 255 `0`'s.
191
+
If we look at the underlying hex representation of these numbers, they are all greater than or equal to the smallest integer that can be represented using `int256`, which is `0x8000000000000000000000000000000000000000000000000000000000000000`. The integer's binary representation is a `1` followed by 255 `0`'s.
192
192
193
193
To obtain the negative value of an integer in a two's complement system, we flip the underlying bits and add `1`: `-a = ~a + 1`.
194
194
An example illustrates this.
@@ -421,7 +421,7 @@ On a 64-bit system, integer addition works in the same way as before.
421
421
= 0x0000000000000001 // int64(1)
422
422
```
423
423
424
-
However, when performing the same calculations on a 256-bit machine, we need to extend the sign of the int64 value over all unused bits,
424
+
However, when performing the same calculations on a 256-bit machine, we need to extend the sign of the `int64` value over all unused bits,
425
425
otherwise the value won't be interpreted correctly.
426
426
427
427
```solidity
@@ -505,59 +505,59 @@ function overflowInt64(int256 value) public pure returns (bool overflow) {
505
505
```
506
506
507
507
We can simplify the expression to a single comparison if we can shift the disjointed number domain back so that it's connected.
508
-
To accomplish this, we subtract the smallest negative int64`type(int64).min` from a value (or add the underlying unsigned value).
509
-
A better way to understand this is by visualizing the signed integer number domain in relation to the unsigned domain (which is demonstrated here using int128).
508
+
To accomplish this, we subtract the smallest negative `int64` (`type(int64).min`) from a value (or add the underlying unsigned value).
509
+
A better way to understand this is by visualizing the signed integer number domain in relation to the unsigned domain (which is demonstrated here using `int128`).
├────\underset{\hskip -3.5em - 2^{127}}─\underset{\hskip -0.4 em -1}{─}┤
546
546
}
547
547
$$
548
548
549
549
Note that the scales of the number ranges in the previous section do not accurately depict the magnitude of numbers that are representable with the different types and only serve as a visualization. We can represent twice as many numbers with only one additional bit, yet the uint256 domain has twice the number of bits compared to uint128.
550
550
551
-
After subtracting `type(int128).min` (or adding $2^{127}$) and essentially shifting the domains to the right, we get the following, connected set of values.
551
+
After subtracting `type(int128).min` (or adding `2**127`) and essentially shifting the domains to the right, we get the following, connected set of values.
= 0x0000000000000000800000000000000000000000000000000000000000000000 // type(int192).min (when seen as a int192)
688
+
= 0x0000000000000000800000000000000000000000000000000000000000000000 // type(int192).min (when looking at the first 192 bits)
689
689
```
690
690
691
691
A method to address this issue is to always start by sign-extending or cleaning the result before attempting to reconstruct the other multiplicand.
@@ -715,9 +715,10 @@ function checkedMulInt192_2(int192 a, int192 b) public pure returns (int192 c) {
715
715
In conclusion, we hope this article has served as an informative guide on signed integer arithmetic within the EVM and the two's complement system.
716
716
We have explored:
717
717
718
-
- the added complexity from handling signed over unsigned integers
719
-
- the intricacies involved in managing sub 32-byte types
720
-
- the significance of `signextend` and opcodes related to signed integers
721
-
- the importance of bit-cleaning
718
+
- How the EVM makes use of the two's complement representation
719
+
- How integer values are interpreted as signed or unsigned depending on the opcodes used
720
+
- The added complexity from handling arithmetic for signed vs. unsigned integers
721
+
- The intricacies involved in managing sub 32-byte types
722
+
- The importance of bit-cleaning and the significance of `signextend`
722
723
723
724
While low-level optimizations are attractive, they are also heavily error-prone. This article aims to deepen one's understanding of low-level arithmetic, to reduce these risks. Nevertheless, it is crucial to integrate custom low-level optimizations only after thorough manual analysis, automated testing, and to document any non-obvious assumptions.
0 commit comments