Solidity 带符号整数

本文详细介绍了 Solidity 中的有符号整数及其在 EVM 层面的使用方式,重点解释了两补码(Two’s Complement)表示法及其在加减乘除等算术运算中的应用。

Solidity 签名整数允许在智能合约中使用负数。本文档记录了它们在 EVM 级别上的使用。假定读者对 EVM 和二进制数有基本的了解。

二的补码解释

Solidity 和 EVM 使用二的补码表示法表示签名整数

像所有数据类型一样,Solidity 仍然使用32字节的字来表示签名整数。在 EVM 中,没有任何语义指示该类型,就像没有任何指示说明一个32字节的插槽实际上是一个布尔值、一个地址或一个160位数一样。该值在编译时被“视为”负一。

因为你可以使用“type(int256).max”获得一个整数的最大值,或者使用.min字段获取最小值。判断一个数字是正是负需要额外的一位,因此它只能存储比无符号版本少一位的数字。

一的补码意味着一个 uint256 变成一个 uint255,最左边的一位指示它是正数还是负数。如果 EVM 使用一的补码。这会意味着 type(int256).max == absoluteValue(type(int256.min)),但情况并非如此。二的补码负数的最大幅度比正数的最大幅度大一。例如,int8 的最大正数是 127,但 int8 的最大负数是 -128。

二的补码算术的模式和示例

与其进行一堆数学证明,不如使用一些真实的例子(这并不是为了证明二的补码算术,仅供感兴趣的读者参考文献)。

让我们使用 int8 来使示例更具可读性。以下是二进制而非十六进制。

int8(0) == 0000 0000
type(int8).max == 0111 1111
type(int8).min == 1000 0000

看到 +1 和 -1 的表示是说明性的:

int8(1) == 0000 0001
int8(-1) == 1111 1111

让我们向下计数,以便一个模式变得明显:

int8(-2) == 1111 1110
int8(-3) == 1111 1101
int8(-4) == 1111 1100
int8(-5) == 1111 1011

你可以大致认为二的补码负数是“向下计数”。

这是二的补码的一个有趣特性。-2 + -2 应该等于 -4,而在二的补码中允许溢出的加法使得这一点得以实现。以下是在 Python 中使用二的补码表示将 -2 相加的示例

>>> (int(b'11111110', 2) + int(b'11111110', 2)) % 256
252
>>> bin(252)
'0b11111100'

这与上面预期的模式相匹配。

如果我们将 +4 加到 -2 上呢?我们应该得到 +2。让我们看看实际情况

>>> # -2 + 4
>>> (int(b'11111110', 2) + int(b'00000100', 2)) % 256
>>> 2

只有在两个数字都是二的补码表示时,这才有效。Solidity 不允许将无符号和签名整数相加,因为这种情况下意图是不明确的。

二的补码还可以处理乘法。-2 和 -2 的预期结果是 +4,读者可参考之前的代码来验证这一点。

这不适用于所有算术运算

二的补码不需要对加法、减法、乘法,甚至是左移操作(<<)进行更改。这些对应于 EVM 操作码 ADD、SUB、MUL 和 SHL。我们将在本教程的后面部分讨论左移为何仍在二的补码中有效。

然而,乘法、取模、右移和转换为更大的签名整数无法使用签名方法完成,并且需要自己的操作码。类似地,传统比较运算符将无法工作,因为负数“看起来”比正数更大。

以太坊签名算术操作码

sdiv

Gas成本:5

SDIV,或签名除法,用于除法操作签名数字。这个操作码在如下代码中被用作为后台。

function divide(int256 a, int256 b) public pure returns (int256 quotient) 
{
    quotient = a / b;
}

smod

Gas成本:5

由于二的补码算术为除法需要自己的操作码,因此对取模(余数)同样适用。

function divide(int256 a, int256 b) public pure returns (int256 remainder) 
{
    remainder = a % b;
}

slt 和 sgt

Gas成本:3

为了比较签名数字的幅度,我们首先需要确定它是正数还是负数,然后比较幅度。这些操作码一步到位地完成了这个操作。

与无符号对应物一样,避免使用 >= 和 <= 会更节省Gas成本,尽量使用严格不等式运算符。

sar – 签名算术右移

Gas成本:3

SAR 是一个非常少用的操作码,但它将出现在编译此 Solidity 代码的结果中。请注意 x 是整数,y 是无符号整数。

contract SarExample {
    function main(int256 x, uint256 y) public pure returns (int256 res) {
        res = x >> y;
    }
}

我们应该如何理解这一点?在普通的无符号数字中,右移一位的位效果相当于除以二,右移两位的位效果相当于除以四,等等。

uint256 x = 8 >> 2; // x = 2
uint256 y = 4 >> 1; // y = 2

如果你对签名整数执行此操作,这种现象则得以保留。

int256 x = -8 >> 2; // x = -2
int256 y = -4 >> 1; // y = -2

为什么没有 SAL(签名算术左移)操作码?在以下示例中会发生什么?

int256 x = -8 &lt;&lt; 2; // x = -32
int256 y = -4 &lt;&lt; 1; // y = -8

我们分别乘以 4 和 2。在二的补码中,左移仍然按预期保留数字。

在内部,使用了常规的 SHL(左移)操作码。对于算术左移,不需要特殊情况。这看起来可能有些直观,因为右侧位变为零时数字会增大。但请记住,在二的补码中,最大负值是当最左边的一位为 1,所有其他位为零时。

signextend evm

Gas成本:5

比 256 位小的签名整数将有前导零。然而,二的补码负数总是以最左边的一位为 1 开头。因此,如果一个二的补码整数被提升为更大的类型,值将从负变为正,因为最左边的位将为零。Signextend 无缝地处理此过渡。

signextend solidity

你不能在 Solidity 中直接使用 signextend,但它在将较小的整数转换为较大的整数时用于后台。以下代码在其编译字节码中包含 signextend 操作码,将 int8 转换为 int256。

contract SignExtendExample {
    function main(int8 x) public pure returns (int256 res) {
        res = x;
    }
}

此时应该显而易见,较大的整数无法转换为较小的整数。

了解更多

在我们的专家Solidity 培训课程中学习更多高级主题。

最初发布于 2023 年 4 月 11 日

  • 原文链接: rareskills.io/post/signe...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
RareSkills
RareSkills
https://www.rareskills.io/